Jackson 工具类详解:ObjectMapper 配置、泛型擦除、TypeReference 与 JavaType
一、前言
在 Java 项目中,JSON 转换是非常常见的功能。前后端接口交互、配置解析、对象缓存、日志记录等场景中,都会用到对象和 JSON 字符串之间的转换。
常见的 JSON 工具有 Gson、FastJson、Jackson。实际企业项目中,Jackson 使用得非常多,尤其是在 Spring Boot 中,默认的 JSON 处理框架就是 Jackson。
本文主要围绕一个 JsonUtil 工具类展开,重点说明以下几个问题:
1. ObjectMapper 常见配置是什么意思
2. Java 时间类型为什么需要额外配置
3. 什么是魔法值,为什么要提取常量
4. Gson、FastJson、Jackson 有什么区别
5. 什么是泛型擦除
6. 为什么 JSON 转 List 时容易变成 LinkedHashMap
7. TypeReference 和 JavaType 分别解决什么问题
8. 如何动态处理 List、Map、List<Map<String, T>> 这种泛型结构
二、JsonUtil 工具类整体结构
项目中的 JSON 工具类一般会封装一个全局的 ObjectMapper,然后提供常用方法:
public class JsonUtil {
private static ObjectMapper OBJECT_MAPPER;
static {
OBJECT_MAPPER = JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false)
.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false)
.configure(MapperFeature.USE_ANNOTATIONS, false)
.addModule(new JavaTimeModule())
.addModule(new SimpleModule()
.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
)
.defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT))
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
}
private JsonUtil() {
}
}
这里的核心就是 ObjectMapper。
ObjectMapper 是 Jackson 中最重要的对象,主要负责:
1. Java 对象转 JSON 字符串
2. JSON 字符串转 Java 对象
3. 控制序列化和反序列化规则
4. 控制日期格式
5. 处理泛型类型
6. 注册自定义序列化器和反序列化器
三、Jackson 核心配置详解
1. 忽略未知字段
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
这个配置用于反序列化。
所谓反序列化,就是把 JSON 字符串转换成 Java 对象。
例如 JSON 中有一个字段:
{
"id": 1,
"name": "北京",
"extraField": "多余字段"
}
但是 Java 类中只有:
public class TestRegion {
private Long id;
private String name;
}
也就是说,Java 类中没有 extraField 这个属性。
默认情况下,Jackson 可能会因为这个未知字段报错。设置为 false 后,Jackson 会忽略 JSON 中多出来的字段。
这样做的好处是提高兼容性。比如前端多传了一个字段,或者第三方接口增加了新字段,后端不会因为这个字段不存在而直接转换失败。
2. 日期不转时间戳
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
这个配置用于序列化。
所谓序列化,就是把 Java 对象转换成 JSON 字符串。
如果 Java 对象中有日期字段,Jackson 默认可能会把日期转成时间戳。
例如:
{
"createTime": 1718000000000
}
这种格式虽然程序能处理,但是可读性比较差。
设置为 false 后,可以让日期按照指定格式输出,例如:
{
"createTime": "2026-06-11 14:30:00"
}
这种格式更适合前后端接口展示和日志排查。
3. 空对象不报错
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
这个配置用于序列化。
如果一个 Java 对象没有任何可序列化的属性,Jackson 默认可能会报错。
设置为 false 后,即使对象没有实际字段,也不会直接报错,通常会序列化成一个空对象:
{}
这个配置可以避免某些空对象导致整个 JSON 转换失败。
4. 无效子类型不直接失败
.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false)
这个配置用于反序列化。
当 JSON 中携带的类型信息和 Java 中预期的子类型不匹配时,默认可能会报错。
设置为 false 后,Jackson 会尽量继续处理,而不是直接抛出异常。
这个配置主要用于存在多态类型、子类类型标识、复杂继承结构的场景。
5. 日期作为 Map 的 key 时不转时间戳
.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false)
这个配置主要影响 Map 的 key。
例如有这种结构:
Map<Date, String> map;
如果 Date 类型作为 Map 的 key,默认可能会被转成时间戳格式。
设置为 false 后,日期 key 会按照日期格式进行序列化,而不是直接转成时间戳。
6. 不使用 Jackson 注解
.configure(MapperFeature.USE_ANNOTATIONS, false)
Jackson 支持很多注解,例如:
@JsonFormat
@JsonIgnore
@JsonProperty
@JsonInclude
这些注解可以控制字段名称、日期格式、是否忽略字段等。
设置为 false 后,Jackson 会忽略这些注解,更多依赖全局配置来处理序列化和反序列化规则。
这个配置适合希望统一管理 JSON 转换规则的场景。
不过要注意,如果项目中大量使用了 Jackson 注解,关闭这个配置可能会导致原本的注解失效。因此这个配置要结合项目实际情况使用。
7. 支持 Java 8 时间类型
.addModule(new JavaTimeModule())
Java 8 以后常用的时间类型有:
LocalDate
LocalDateTime
LocalTime
这些类型不属于传统的 java.util.Date。
Jackson 默认对 Java 8 时间类型的支持不够完整,所以需要添加:
JavaTimeModule
这个模块由 jackson-datatype-jsr310 提供,用于支持 Java 8 日期时间 API。
8. 自定义 LocalDateTime 格式
.addModule(new SimpleModule()
.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
)
这段配置是专门处理 LocalDateTime 的。
序列化时:
LocalDateTime -> JSON 字符串
反序列化时:
JSON 字符串 -> LocalDateTime
例如 Java 对象中有:
private LocalDateTime createTime;
序列化后可以变成:
{
"createTime": "2026-06-11 14:30:00"
}
反序列化时,也可以把这个字符串重新转成 LocalDateTime。
9. 统一传统 Date 格式
.defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT))
这个配置主要用于传统日期类型,比如:
java.util.Date
java.util.Calendar
如果项目中既有 Date,又有 LocalDateTime,那么通常需要同时配置:
1. defaultDateFormat 处理 Date
2. JavaTimeModule / LocalDateTimeSerializer 处理 LocalDateTime
10. null 字段不序列化
.serializationInclusion(JsonInclude.Include.NON_NULL)
这个配置表示:只序列化非 null 的字段。
例如 Java 对象:
public class User {
private Long id;
private String name;
private String address;
}
如果 address 是 null,序列化后不会出现在 JSON 中。
输出结果可能是:
{
"id": 1,
"name": "张三"
}
而不是:
{
"id": 1,
"name": "张三",
"address": null
}
这样可以减少 JSON 体积,也可以避免前端收到大量 null 字段。
四、魔法值问题
在代码中直接写死的固定值,通常称为魔法值。
例如:
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
这里的:
yyyy-MM-dd HH:mm:ss
就是一个魔法值。
魔法值的问题主要有两个。
第一,代码可读性差。别人看到这个字符串时,需要自己判断它代表什么含义。
第二,后期维护困难。如果多个地方都写了同一个格式,将来要修改日期格式,就需要到很多地方逐个修改。
更好的做法是提取成常量:
public class CommonConstants {
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
}
然后使用:
DateTimeFormatter.ofPattern(CommonConstants.STANDARD_FORMAT)
这样代码含义更清楚,也方便统一修改。
在工具类中,已经使用了:
.defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT))
但是 LocalDateTimeSerializer 和 LocalDateTimeDeserializer 中仍然直接写了:
"yyyy-MM-dd HH:mm:ss"
这部分也可以统一改成常量:
private static final DateTimeFormatter STANDARD_DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern(CommonConstants.STANDARD_FORMAT);
然后:
.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(STANDARD_DATE_TIME_FORMATTER))
.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(STANDARD_DATE_TIME_FORMATTER))
这样整个工具类的日期格式就统一了。
五、常见 JSON 工具对比
1. Gson
Gson 是 Google 开发的 JSON 处理库。
它的特点是简单、轻量、容易上手。
适合简单的对象和 JSON 互转场景。
但是在复杂日期处理、复杂泛型、企业级定制方面,Gson 的功能相对没有 Jackson 丰富。
2. FastJson
FastJson 是阿里巴巴开源的 JSON 处理库。
它早期以性能较高、使用简单著称。
但是 FastJson 以前出现过一些安全漏洞,所以在实际项目中使用时,需要特别关注版本安全问题,并尽量使用安全版本和安全模式。
3. Jackson
Jackson 是 FasterXML 开发的 JSON 处理库。
它功能强大,生态成熟,支持 JSON、XML、YAML 等多种数据格式。
Spring Boot 默认也使用 Jackson 作为 JSON 处理框架。
Jackson 的优点是:
1. 功能丰富
2. 性能较好
3. 支持复杂泛型
4. 支持丰富的配置
5. 和 Spring Boot 集成度高
缺点是配置项较多,初学时学习成本比 Gson 略高。
综合来看,如果是企业级 Spring Boot 项目,Jackson 是非常常见的选择。
六、对象转 JSON 字符串
工具类中对象转字符串方法如下:
public static <T> String obj2String(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : OBJECT_MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.warn("Parse Object to String error : {}", e.getMessage());
return null;
}
}
这个方法的作用是:
Java 对象 -> JSON 字符串
例如:
TestRegion region = new TestRegion();
region.setId(1L);
region.setName("北京");
region.setFullName("北京市");
region.setCode("110000");
String json = JsonUtil.obj2String(region);
输出可能是:
{"id":1,"name":"北京","fullName":"北京市","code":"110000"}
这里有一个特殊判断:
obj instanceof String ? (String) obj : OBJECT_MAPPER.writeValueAsString(obj)
如果传进来的对象本身就是字符串,就直接返回,不再重复转 JSON。
七、格式化输出 JSON
工具类中还有一个格式化输出方法:
public static <T> String obj2StringPretty(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj :
OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.warn("Parse Object to String error : {}", e.getMessage());
return null;
}
}
这里和普通 obj2String 的区别是:
writerWithDefaultPrettyPrinter()
它会让 JSON 字符串变得更美观。
普通 JSON:
{"id":1,"name":"北京","fullName":"北京市","code":"110000"}
格式化后:
{
"id" : 1,
"name" : "北京",
"fullName" : "北京市",
"code" : "110000"
}
这种格式更适合调试、日志查看和接口测试。
八、普通对象反序列化
普通对象反序列化方法如下:
public static <T> T string2Obj(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return clazz.equals(String.class) ? (T) str : OBJECT_MAPPER.readValue(str, clazz);
} catch (JsonProcessingException e) {
log.warn("Parse String to Object error : {}", e.getMessage());
return null;
}
}
这个方法适合 JSON 对应一个普通 Java 对象的情况。
例如 JSON:
{
"id": 1,
"name": "北京",
"fullName": "北京市",
"code": "110000"
}
Java 调用:
TestRegion region = JsonUtil.string2Obj(jsonStr, TestRegion.class);
这时 Jackson 能明确知道目标类型是:
TestRegion
所以可以正常转换。
九、什么是泛型擦除
泛型擦除是 Java 泛型中的一个重要概念。
简单来说,Java 编译之后,很多泛型信息在运行时会被擦除。
例如代码中写的是:
List<TestRegion> list;
但是运行时,JVM 很多时候只知道它是:
List
至于 List 里面具体装的是 TestRegion,这个信息在运行时不一定完整保留。
所以如果我们这样写:
List list = JsonUtil.string2Obj(jsonStr, List.class);
Jackson 只知道目标类型是 List,但是不知道 List 里面的元素类型。
这时如果 JSON 数组里面是对象,Jackson 默认会把里面的对象转换成:
LinkedHashMap
而不是转换成我们想要的:
TestRegion
十、Jackson 默认转换规则
当 Jackson 不知道具体目标类型时,会根据 JSON 结构选择默认 Java 类型。
常见规则是:
JSON 的 {} -> LinkedHashMap
JSON 的 [] -> ArrayList
例如 JSON 是:
[
{
"id": 1,
"name": "北京",
"fullName": "北京市",
"code": "110000"
}
]
如果直接写:
List list = OBJECT_MAPPER.readValue(jsonStr, List.class);
最终得到的不是:
List<TestRegion>
而是:
ArrayList<LinkedHashMap>
也就是说:
外层 [] 变成 ArrayList
里面 {} 变成 LinkedHashMap
这就是很多人遇到的:
LinkedHashMap cannot be cast to TestRegion
的根本原因。
不是 Jackson 出错了,而是我们没有告诉 Jackson 泛型里面具体是什么类型。
十一、TypeReference 解决固定复杂泛型
如果泛型结构是固定的,可以使用 TypeReference。
工具类方法:
public static <T> T string2Obj(String str, TypeReference<T> valueTypeRef) {
if (StringUtils.isEmpty(str) || valueTypeRef == null) {
return null;
}
try {
return OBJECT_MAPPER.readValue(str, valueTypeRef);
} catch (JsonProcessingException e) {
log.warn("Parse String to Object error : {}", e.getMessage());
return null;
}
}
例如 JSON 是:
[
{
"id": 1,
"name": "北京",
"fullName": "北京市",
"code": "110000"
}
]
想转成:
List<TestRegion>
可以写:
List<TestRegion> regionList = JsonUtil.string2Obj(
jsonStr,
new TypeReference<List<TestRegion>>() {}
);
这里的:
new TypeReference<List<TestRegion>>() {}
把完整泛型信息告诉了 Jackson。
Jackson 就知道:
外层是 List
里面的元素是 TestRegion
所以不会再把元素转成 LinkedHashMap。
十二、TypeReference 后面的 {} 是什么
很多人第一次看到这个写法会有疑问:
new TypeReference<List<TestRegion>>() {}
为什么最后还有一个 {}?
这个 {} 表示创建了一个匿名内部类。
Jackson 正是通过这个匿名内部类,读取到父类上携带的泛型信息。
所以这个 {} 不是多余的,它是 TypeReference 这种写法的关键。
如果没有这个匿名内部类,复杂泛型信息就很难被完整保留下来。
十三、TypeReference 处理多层嵌套泛型
如果 JSON 是这种结构:
[
{
"110000": {
"id": 1,
"name": "北京",
"fullName": "北京市",
"code": "110000"
}
}
]
外层是数组 [],里面是对象 {},对象中的 value 又是一个具体的 Java 对象。
对应 Java 类型是:
List<Map<String, TestRegion>>
含义是:
外层 [] -> List
里面 {} -> Map
Map 的 key -> String
Map 的 value -> TestRegion
写法如下:
List<Map<String, TestRegion>> mapList = JsonUtil.string2Obj(
jsonStr,
new TypeReference<List<Map<String, TestRegion>>>() {}
);
这个适合类型固定的场景。
如果代码里已经明确知道就是:
List<Map<String, TestRegion>>
那么用 TypeReference 最简单。
十四、JavaType 解决动态泛型
TypeReference 适合固定泛型。
但是如果类型是调用方法时传进来的,就更适合使用 JavaType。
例如工具类中有方法:
public static <T> List<T> string2List(String str, Class<T> clazz)
这里的 T 不是写死的。
调用时可能是:
JsonUtil.string2List(jsonStr, TestRegion.class);
JsonUtil.string2List(jsonStr, User.class);
JsonUtil.string2List(jsonStr, Order.class);
这种情况下,泛型类型是在运行时才确定的。
所以需要用 Jackson 的 TypeFactory 动态构造 JavaType。
十五、动态解决 List 泛型擦除
工具类中的 List 转换方法:
public static <T> List<T> string2List(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
JavaType javaType = OBJECT_MAPPER
.getTypeFactory()
.constructParametricType(List.class, clazz);
try {
return OBJECT_MAPPER.readValue(str, javaType);
} catch (JsonProcessingException e) {
log.warn("Parse String to Object error : {}", e.getMessage());
return null;
}
}
核心代码是:
constructParametricType(List.class, clazz)
它的意思是动态构造:
List<T>
例如调用:
List<TestRegion> list = JsonUtil.string2List(jsonStr, TestRegion.class);
这时:
clazz = TestRegion.class
所以最终构造出来的类型就是:
List<TestRegion>
这样 Jackson 就知道 List 里面每一个元素都是 TestRegion,不会再默认转成 LinkedHashMap。
十六、如果想明确使用 ArrayList
在实际开发中,一般建议返回接口类型:
List<T>
而不是具体实现类:
ArrayList<T>
因为接口更灵活。
但是如果确实想明确构造 ArrayList<T>,可以写:
public static <T> ArrayList<T> string2ArrayList(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
JavaType javaType = OBJECT_MAPPER
.getTypeFactory()
.constructCollectionType(ArrayList.class, clazz);
try {
return OBJECT_MAPPER.readValue(str, javaType);
} catch (JsonProcessingException e) {
log.warn("Parse String to ArrayList error : {}", e.getMessage());
return null;
}
}
核心代码是:
constructCollectionType(ArrayList.class, clazz)
它表示构造:
ArrayList<T>
例如:
ArrayList<TestRegion> list = JsonUtil.string2ArrayList(jsonStr, TestRegion.class);
十七、动态解决 Map 泛型擦除
工具类中的 Map 转换方法:
public static <T> Map<String, T> string2Map(String str, Class<T> valueClass) {
if (StringUtils.isEmpty(str) || valueClass == null) {
return null;
}
JavaType javaType = OBJECT_MAPPER
.getTypeFactory()
.constructMapType(LinkedHashMap.class, String.class, valueClass);
try {
return OBJECT_MAPPER.readValue(str, javaType);
} catch (JsonProcessingException e) {
log.warn("Parse String to Object error : {}", e.getMessage());
return null;
}
}
核心代码是:
constructMapType(LinkedHashMap.class, String.class, valueClass)
它的意思是构造:
LinkedHashMap<String, T>
三个参数分别表示:
LinkedHashMap.class -> Map 的具体实现类
String.class -> key 的类型
valueClass -> value 的类型
例如 JSON:
{
"110000": {
"id": 1,
"name": "北京",
"fullName": "北京市",
"code": "110000"
}
}
调用:
Map<String, TestRegion> map = JsonUtil.string2Map(jsonStr, TestRegion.class);
最终构造出来的类型就是:
LinkedHashMap<String, TestRegion>
这里用 LinkedHashMap 是因为 JSON 对象 {} 本质上就是键值对结构。并且 LinkedHashMap 可以保持字段的插入顺序。
十八、为什么 Map 方法里写 LinkedHashMap,不写 ArrayList
因为这个方法本身就是处理 Map 结构的:
public static <T> Map<String, T> string2Map(String str, Class<T> valueClass)
它适合处理最外层是 {} 的 JSON。
例如:
{
"110000": {
"id": 1,
"name": "北京"
}
}
这种结构应该用 Map 接收。
但是如果 JSON 最外层是数组:
[
{
"id": 1,
"name": "北京"
}
]
那就不能用 string2Map,而应该用 string2List。
可以这样记:
JSON 最外层是 {} -> 用 Map / LinkedHashMap
JSON 最外层是 [] -> 用 List / ArrayList
所以:
constructMapType(LinkedHashMap.class, String.class, valueClass)
只是在构造 Map 类型。
如果要构造数组或集合类型,应该用:或者:
constructParametricType(...)
十九、动态解决 List<Map<String, T>> 泛型擦除
如果 JSON 是这种嵌套结构:
[
{
"110000": {
"id": 1,
"name": "北京",
"fullName": "北京市",
"code": "110000"
}
},
{
"310000": {
"id": 2,
"name": "上海",
"fullName": "上海市",
"code": "310000"
}
}
]
它的结构是:
外层 [] -> List
里面 {} -> Map
Map 的 key -> String
Map 的 value -> T
Java 类型就是:
List<Map<String, T>>
动态写法如下:
public static <T> List<Map<String, T>> string2ListMap(String str, Class<T> valueClass) {
if (StringUtils.isEmpty(str) || valueClass == null) {
return null;
}
JavaType mapType = OBJECT_MAPPER
.getTypeFactory()
.constructMapType(LinkedHashMap.class, String.class, valueClass);
JavaType listType = OBJECT_MAPPER
.getTypeFactory()
.constructCollectionType(List.class, mapType);
try {
return OBJECT_MAPPER.readValue(str, listType);
} catch (JsonProcessingException e) {
log.warn("Parse String to List<Map<String, T>> error : {}", e.getMessage());
return null;
}
}
调用方式:
List<Map<String, TestRegion>> list =
JsonUtil.string2ListMap(jsonStr, TestRegion.class);
这个方法的构造过程分成两步。
第一步,先构造里面的 Map 类型:
Map<String, TestRegion>
对应代码:
JavaType mapType = OBJECT_MAPPER
.getTypeFactory()
.constructMapType(LinkedHashMap.class, String.class, TestRegion.class);
第二步,再构造外层的 List 类型:
List<Map<String, TestRegion>>
对应代码:这样 Jackson 就知道完整结构了。
二十、多层嵌套泛型如何处理
有些接口返回的数据结构可能更加复杂,例如:
TianYanChaResponse<TianYanChaListResult<TianYanChaResultItem190>>
这种类型属于多层嵌套泛型。
如果类型是固定的,可以直接用 TypeReference:
TianYanChaResponse<TianYanChaListResult<TianYanChaResultItem190>> response =
OBJECT_MAPPER.readValue(
jsonStr,
new TypeReference<TianYanChaResponse<TianYanChaListResult<TianYanChaResultItem190>>>() {}
);
如果类型需要动态传入,就可以用 JavaType 一层一层构造。
示例:
JavaType itemType = OBJECT_MAPPER
.getTypeFactory()
.constructType(TianYanChaResultItem190.class);
JavaType listResultType = OBJECT_MAPPER
.getTypeFactory()
.constructParametricType(TianYanChaListResult.class, itemType);
JavaType responseType = OBJECT_MAPPER
.getTypeFactory()
.constructParametricType(TianYanChaResponse.class, listResultType);
TianYanChaResponse<TianYanChaListResult<TianYanChaResultItem190>> response =
OBJECT_MAPPER.readValue(jsonStr, responseType);
核心思想就是:
二十一、TypeReference 和 JavaType 的区别
TypeReference 和 JavaType 都是用来解决泛型擦除问题的。
它们的目的都是告诉 Jackson 完整的泛型类型。
但是使用场景不同。
1. TypeReference 适合固定泛型
例如:
List<Map<String, TestRegion>> list = JsonUtil.string2Obj(
jsonStr,
new TypeReference<List<Map<String, TestRegion>>>() {}
);
这里的类型在写代码的时候已经确定了。
也就是说,代码里已经写死了:
List<Map<String, TestRegion>>
这种情况使用 TypeReference 最直观。
2. JavaType 适合动态泛型
例如:
public static <T> List<T> string2List(String str, Class<T> clazz)
这里的 T 不是写死的,而是调用方法时传进来的。
可能是:
TestRegion.class
User.class
Order.class
这种情况就更适合用 JavaType。
因为 JavaType 可以在运行时动态拼接出完整的泛型类型。
二十二、完整工具方法整理
1. 对象转 JSON 字符串
public static <T> String obj2String(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : OBJECT_MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.warn("Parse Object to String error : {}", e.getMessage());
return null;
}
}
2. 对象转格式化 JSON 字符串
public static <T> String obj2StringPretty(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj :
OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.warn("Parse Object to String error : {}", e.getMessage());
return null;
}
}
3. JSON 字符串转普通对象
public static <T> T string2Obj(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return clazz.equals(String.class) ? (T) str : OBJECT_MAPPER.readValue(str, clazz);
} catch (JsonProcessingException e) {
log.warn("Parse String to Object error : {}", e.getMessage());
return null;
}
}
4. JSON 字符串转固定复杂泛型
public static <T> T string2Obj(String str, TypeReference<T> valueTypeRef) {
if (StringUtils.isEmpty(str) || valueTypeRef == null) {
return null;
}
try {
return OBJECT_MAPPER.readValue(str, valueTypeRef);
} catch (JsonProcessingException e) {
log.warn("Parse String to Object error : {}", e.getMessage());
return null;
}
}
5. JSON 字符串转 List
public static <T> List<T> string2List(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
JavaType javaType = OBJECT_MAPPER
.getTypeFactory()
.constructParametricType(List.class, clazz);
try {
return OBJECT_MAPPER.readValue(str, javaType);
} catch (JsonProcessingException e) {
log.warn("Parse String to Object error : {}", e.getMessage());
return null;
}
}
6. JSON 字符串转 Map<String, T>
public static <T> Map<String, T> string2Map(String str, Class<T> valueClass) {
if (StringUtils.isEmpty(str) || valueClass == null) {
return null;
}
JavaType javaType = OBJECT_MAPPER
.getTypeFactory()
.constructMapType(LinkedHashMap.class, String.class, valueClass);
try {
return OBJECT_MAPPER.readValue(str, javaType);
} catch (JsonProcessingException e) {
log.warn("Parse String to Object error : {}", e.getMessage());
return null;
}
}
7. JSON 字符串转 List<Map<String, T>>
public static <T> List<Map<String, T>> string2ListMap(String str, Class<T> valueClass) {
if (StringUtils.isEmpty(str) || valueClass == null) {
return null;
}
JavaType mapType = OBJECT_MAPPER
.getTypeFactory()
.constructMapType(LinkedHashMap.class, String.class, valueClass);
JavaType listType = OBJECT_MAPPER
.getTypeFactory()
.constructCollectionType(List.class, mapType);
try {
return OBJECT_MAPPER.readValue(str, listType);
} catch (JsonProcessingException e) {
log.warn("Parse String to List<Map<String, T>> error : {}", e.getMessage());
return null;
}
}
二十三、实际开发中怎么选择
可以按下面的规则选择:
普通对象:
使用 Class<T>
固定复杂泛型:
使用 TypeReference<T>
动态 List<T>:
使用 JavaType + constructParametricType 或 constructCollectionType
动态 Map<String, T>:
使用 JavaType + constructMapType
动态 List<Map<String, T>>:
先构造 Map 的 JavaType,再构造 List 的 JavaType
再简单一点:
二十四、总结
Jackson 是一个功能强大的 JSON 处理框架。
普通对象转换比较简单,直接使用 Class<T> 就可以。
但是遇到泛型结构时,只写 List.class 或 Map.class 是不够的,因为 Java 存在泛型擦除。
当 Jackson 不知道具体泛型时:
JSON 的 {} 会默认转成 LinkedHashMap
JSON 的 [] 会默认转成 ArrayList
所以如果我们没有指定完整泛型类型,就容易出现:
LinkedHashMap cannot be cast to Xxx
这类问题。
解决方式主要有两种:
1. TypeReference
2. JavaType
TypeReference 适合固定的复杂泛型。
JavaType 适合运行时动态构造泛型。
最终可以记住一句话:
普通对象用 Class,固定复杂泛型用 TypeReference,动态泛型用 JavaType。
在实际项目中,把这些方法封装到 JsonUtil 工具类中,可以减少重复代码,也可以避免因为泛型擦除导致的类型转换问题。