如何使用Jackson反序列化泛型List

huangapple go评论118阅读模式
英文:

How to deserialize generic List<T> with Jackson?

问题

我多年来一直在使用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的列表。我理解ObjectMapper在类型为Object时反序列化为LinkedHashMap,我在这里阅读了关于这一点的解释:

https://stackoverflow.com/questions/6846244/jackson-and-generic-type-reference

然而,为什么它能够将List<LinkedHashMap>赋值给List<Foo>呢?至少这不应该引发某种形式的ClassCastException吗?

另外,是否有任何方法可以在Java的类型系统中实现这一点?

注意:下面的方法声明存在相同的问题,因为额外的参数对于确定T并不是必需的:

public static <T> TypeReference<List<T>> listOf(Class<T> ignored) {
    return new TypeReference<List<T>>(){}
}
英文:

I've been using Jackson to serialize/deserialize objects for years and have always found it needlessly complicated to use TypeReference&lt;T&gt; to deserialize List etc. I created a simple helper function:

public static &lt;T&gt; TypeReference&lt;List&lt;T&gt;&gt; list() {
    return new TypeReference&lt;List&lt;T&gt;&gt;(){}
}

With intended use:

List&lt;Foo&gt; foos = objectMapper.readValue(json, list());

And it works! Kind of. When inspecting through the debugger, rather than a list of Foo, it is rather a list of LinkedHashMap. I understand that ObjectMapper deserializes into LinkedHashMap for type Object and I read the explanation for that here:

https://stackoverflow.com/questions/6846244/jackson-and-generic-type-reference

However, why is it able to assign List&lt;LinkedHasMap&gt; to a List&lt;Foo&gt;? At the very least shouldn't that be some sort of ClassCastException?

Also, is there anyway to do this with Java's type system?

NOTE: the following method declaration has the same issue, which makes sense because the additional argument is not needed for T to be determined:

public static &lt;T&gt; TypeReference&lt;List&lt;T&gt;&gt; listOf(Class&lt;T&gt; ignored) {
    return new TypeReference&lt;List&lt;T&gt;&gt;(){}
}

答案1

得分: 12

这是由于Java中的类型擦除(type erasure)机制导致的。在阅读本答案的后续部分之前,请先阅读以下内容:

根据你现在可能已经了解的情况,阅读上述文章后,你的方法在编译后会变成这样:

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("使用'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("使用'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
  }
]

以上示例将输出:

使用'TypeFactory'尝试
[{1}, {22}, {333}]
{1}
使用'TypeReference<List<T>>'尝试
[{id=1}, {id=22}, {id=333}]
在线程"main"中出现异常:java.lang.ClassCastException: 无法将java.util.LinkedHashMap强制转换为com.example.Id
    at com.example.JsonTypeApp.main(JsonTypeApp.java:27)

另请参考:

英文:

It works like this because of type erasure in Java. Please, read about it before you start reading next part of this answer:

As you probably know right now, after reading above articles, your method after compilation looks like this:

static &lt;T&gt; TypeReference&lt;List&gt; listOf(Class&lt;T&gt; ignored) {
return new TypeReference&lt;List&gt;(){};
}

Jackson will try to find out the most appropriate type for it which will be java.util.LinkedHashMap for a JSON Object. To create irrefutable type you need to use com.fasterxml.jackson.databind.type.TypeFactory class. See below example:

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(&quot;./resource/test.json&quot;).getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(&quot;Try with &#39;TypeFactory&#39;&quot;);
List&lt;Id&gt; ids = mapper.readValue(jsonFile, CollectionsTypeFactory.listOf(Id.class));
System.out.println(ids);
Id id1 = ids.get(0);
System.out.println(id1);
System.out.println(&quot;Try with &#39;TypeReference&lt;List&lt;T&gt;&gt;&#39;&quot;);
List&lt;Id&gt; 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 &lt;T&gt; TypeReference&lt;List&gt; erasedListOf(Class&lt;T&gt; ignored) {
return new TypeReference&lt;List&gt;(){};
}
}
class Id {
private int id;
// getters, setters, toString
}

Above example, for below JSON payload:

[
{
&quot;id&quot;: 1
},
{
&quot;id&quot;: 22
},
{
&quot;id&quot;: 333
}
]

prints:

Try with &#39;TypeFactory&#39;
[{1}, {22}, {333}]
{1}
Try with &#39;TypeReference&lt;List&lt;T&gt;&gt;&#39;
[{id=1}, {id=22}, {id=333}]
Exception in thread &quot;main&quot; java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Id
at com.example.JsonTypeApp.main(JsonTypeApp.java:27)

See also:

huangapple
  • 本文由 发表于 2020年4月11日 09:06:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/61150873.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定