学习 Jackson 封装时候遇到一个泛型问题希望大牛能帮忙解惑

2025 年 8 月 12 日
 puremaker

封装一个转指定实体类 list 的方法,有如下两种

public static <T> List<T> parseList(String jsonString, Class<T> elementClazz) throws Exception {
	ObjectMapper mapper = new ObjectMapper();
    TypeReference<List<T>> typeReference = new TypeReference<List<T>>() {};
    return mapper.readValue(jsonString, typeReference);
}
public static <T> List<T> parseList(String jsonString, TypeReference<List<T>> typeReference) throws Exception {
	ObjectMapper mapper = new ObjectMapper();
	return mapper.readValue(jsonString, typeReference);
}
public static void main(String[] args) throws  Exception {
        String jsonListStr = "[{\"username\":\"pure1\",\"phone\":\"18xxx\"},{\"username\":\"pure2\",\"phone\":\"19xxx\"}]";
        List<User> userList1 = parseList(jsonListStr, User.class);
        List<User> userList2 = parseList(jsonListStr, new TypeReference<List<User>>(){});
}

两个转完的 list 里,第一个 list 里的对象在断点里看实际上是个LinkedHashMap ,是无法正常调用实体类的 get 方法的。第二个 list 里的对象就是真正的User。所以我分别去看了两个方法的 typeReference 对象,第一个方法的 typeReference 对象里的_type 值为“java.util.List<T>”,第二个方法里的 typeReference 对象里的_type 值为“java.util.List<xxx.xxx.entity.User>”。虽然第二个方法可以正常使用,但是封装肯定是为了简便,以TypeReference<List<T>> typeReference作为入参感觉很奇怪,我底层了解的不多,我的认知里在入参的时候 new 一个 TypeReference 和在方法里 new 一个 TypeReference 应该是一样的才对。希望有大牛帮我解惑,或者是不是我第一个方法的代码写的有问题。

在此先谢谢各位了!!!

2548 次点击
所在节点    Java
17 条回复
xtreme1
2025 年 8 月 12 日
JavaType listType = mapper.getTypeFactory().constructCollectionType(List.class, elementClazz);
return mapper.readValue(jsonString, listType);

第一个 T 运行时被擦除了, 单步一下 TypeReference 的构造方法就知道了.
visper
2025 年 8 月 12 日
看起来像是 java 的类型擦除然后第一个方法在里面 new 的时候,里面无法再知道那个是什么类型。
raylax7
2025 年 8 月 12 日
第一种会类型擦除
第二种 new TypeReference<List<User>>() {} 会生成一个单独的类文件 Class$1.class
class Class$1 extends TypeReference<java.util.List<com.example.User>> { } 类型签名保留了 List<User> 的完整信息
调用 getClass().getGenericSuperclass(),就能拿到这个签名,然后解析出 User
dcsuibian
2025 年 8 月 12 日
Java 在编译时会进行类型擦除,大多数会被擦掉。但继承结构中的一部分泛型信息会保存,可以通过反射读到。
lemondev
2025 年 8 月 12 日
第一个 parseList 方法有问题,虽然 typeReference 是 TypeReference<List<T>>,
但是在运行时,T 已经被擦除成 Object 。Jackson 是不知道的,他得到了一个 Object 。具体你可以看编译后的字节码。可以看出来端倪。

这种情况你必须手动构造泛型类型了。

public static <T> List<T> parseList(String jsonString, Class<T> elementClazz) throws Exception {
ObjectMapper mapper = new ObjectMapper();
JavaType javaType = mapper.getTypeFactory()
.constructCollectionType(List.class, elementClazz);
return mapper.readValue(jsonString, javaType);
}
hapeman
2025 年 8 月 12 日
java 的泛型是假泛型,只在编译期有效,编译完成后会所有泛型都会被擦除
puremaker
2025 年 8 月 12 日
@lemondev 非常通透,理解了,感谢感谢
puremaker
2025 年 8 月 12 日
@hapeman 受教了
puremaker
2025 年 8 月 12 日
@raylax7 大概能懂了
xuanbg
2025 年 8 月 13 日
我是这样写的:
public static <T> List<T> toList(String json, Class<T> type) {
try {
return MAPPER.readValue(json.trim(), getJavaType(List.class, type));
} catch (IOException ex) {
throw new BusinessException(ex.getMessage());
}
}
xuanbg
2025 年 8 月 13 日
@xuanbg 实际上 getJavaType 方法就是 5 楼一样的代码
looveh
2025 年 8 月 13 日
最近刚解决这个问题就刷到你这个问题😐
我是 Postgresql 查询的时候把主数据和子数据一次性查询,子数据使用 json 数组作为子数据的一列,然后自定义一个 TypeHandler 将 json 数组转成对象集合;

```java
@MappedJdbcTypes(value = JdbcType.VARCHAR)
public class CustomListTypeHandler<T> extends AbstractJsonTypeHandler<List<T>> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private final Class<T> elementType;
public CustomListTypeHandler(Class<T> elementType) {
this.elementType = elementType;
}

@Override
protected List<T> parse(String json) {
try {
CollectionType collectionType = OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, elementType);
return OBJECT_MAPPER.readValue(json, collectionType);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

@Override
protected String toJson(List<T> obj) {
try {
return OBJECT_MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
```
looveh
2025 年 8 月 13 日
@looveh 话说评论怎么嵌入代码?不支持 md 么
pointerman
2025 年 8 月 13 日
你这样写,每次调用方法都要 new 一个 ObjectMapper ,应该把 ObjectMapper 注入 Spring 容器,然后在工具类中 @Autowired 一个静态的 objectMapper 对象,所有工具类里的所有方法都用这个对象
siweipancc
2025 年 8 月 13 日
虽然大伙讨厌八股文,但是在这个场景八股文还是有点用的。
shiloh595
2025 年 8 月 17 日
🐮
puremaker
2025 年 9 月 9 日
@pointerman 不注入 spring 容器也有好处,调用的时候可以直接以静态方法调用,注入了的话,工具类也得注册成组件,调用还得再类里再 @resource 一下。然后就是封装的方法可以更多样,比如说封装了“全量转为 json 字符串”和“忽略空值转为 json 字符串”,后者就是对 objectMapper 的设置多了 setSerializationInclusion(JsonInclude.Include.NON_NULL),如果用同一个 ObjectMapper 的话,就没办法同时实现这两个方法啦。是这么个道理吧?不知道我解释的对不对

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1151881

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX