如何使用Jackson反序列化泛型List<T>?

12

我多年来一直使用Jackson来序列化/反序列化对象,但发现使用TypeReference<T>来反序列化List等内容过于复杂。因此,我创建了一个简单的帮助函数:

public static <T> TypeReference<List<T>> list() {
    return new TypeReference<List<T>>(){}
}

使用意图:

List<Foo> foos = objectMapper.readValue(json, list());

而且它有效!有点。在通过调试器进行检查时,与其是Foo列表,它实际上是LinkedHashMap列表。我了解ObjectMapperObject类型反序列化为LinkedHashMap,并在此处阅读了解释:Jackson和通用类型引用
然而,为什么能够将List<LinkedHasMap>分配给List<Foo>?至少不应该是某种ClassCastException吗?
另外,有没有办法使用Java的类型系统来解决这个问题?
注意:以下方法声明具有相同的问题,因为不需要额外的参数即可确定T
public static <T> TypeReference<List<T>> listOf(Class<T> ignored) {
    return new TypeReference<List<T>>(){}
}

1
这个回答解决了你的问题吗?Jackson和泛型类型引用 - Savior
请问如何在使用泛型时,使用Jackson的TypeReference? - Savior
2
在运行时,List<LinkedHasMap>List<Foo> 没有区别,因为存在类型擦除。 - Savior
参见Gson反序列化列表和类,擦除在这里是如何工作的?。不同的库,完全相同的问题。 - Andreas
1个回答

12
这是因为Java中的类型擦除。在阅读本答案的下一部分之前,请先阅读相关内容:
- [类型擦除](link1) - [解释Java中的类型擦除](link2) - [Java泛型类型擦除:何时以及发生了什么?](link3)
在阅读上述文章后,你可能已经知道,编译后的方法看起来像这样:
static <T> TypeReference<List> listOf(Class<T> ignored) {
    return new TypeReference<List>(){};
}

Jackson会尝试找到最适合的类型,对于一个JSON对象来说,最适合的类型将是java.util.LinkedHashMap。要创建一个不可否认的类型,您需要使用com.fasterxml.jackson.databind.type.TypeFactory类。请参考下面的示例:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;

import java.io.File;
import java.util.List;

public class JsonTypeApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        ObjectMapper mapper = new ObjectMapper();

        System.out.println("Try with 'TypeFactory'");
        List<Id> ids = mapper.readValue(jsonFile, CollectionsTypeFactory.listOf(Id.class));
        System.out.println(ids);
        Id id1 = ids.get(0);
        System.out.println(id1);

        System.out.println("Try with 'TypeReference<List<T>>'");
        List<Id> maps = mapper.readValue(jsonFile, CollectionsTypeFactory.erasedListOf(Id.class));
        System.out.println(maps);
        Id maps1 = maps.get(0);
        System.out.println(maps1);
    }
}

class CollectionsTypeFactory {
    static JavaType listOf(Class clazz) {
        return TypeFactory.defaultInstance().constructCollectionType(List.class, clazz);
    }

    static <T> TypeReference<List> erasedListOf(Class<T> ignored) {
        return new TypeReference<List>(){};
    }
}

class Id {
    private int id;

    // getters, setters, toString
}

以上示例,针对以下JSON负载:
[
  {
    "id": 1
  },
  {
    "id": 22
  },
  {
    "id": 333
  }
]

打印:

Try with 'TypeFactory'
[{1}, {22}, {333}]
{1}
Try with 'TypeReference<List<T>>'
[{id=1}, {id=22}, {id=333}]
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Id
    at com.example.JsonTypeApp.main(JsonTypeApp.java:27)

另请参阅:

你救了我的一天。我可以问一下你是怎么想到这个解决方案的吗? - Marco Sulla
@MarcoSulla,我已经使用 Jackson 很长时间了,并且在过去的几年中读了很多有关它的文章。你可以看一下我的其他回答 - Michał Ziober

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接