杰克逊和通用类型引用

152
我想使用jackson json库编写一个通用方法,代码如下:
public MyRequest<T> tester() {
    TypeReference<MyWrapper<T>> typeRef = new TypeReference<MyWrapper<T>>();  
    MyWrapper<T> requestWrapper = (MyWrapper<T>) JsonConverter.fromJson(jsonRequest, typeRef);
    return requestWrapper.getRequest();
}

public class MyWrapper<T> {

    private MyRequest<T> request;

    public MyRequest<T> getRequest() {
        return request;
    }

    public void setRequest(MyRequest<T> request) {
        this.request = request;
    }
}

public class MyRequest<T> {
     private List<T> myobjects;
        
     public void setMyObjects(List<T> ets) {
         this.myobjects = ets;
     }

     @NotNull
     @JsonIgnore
     public T getMyObject() {
         return myobjects.get(0);
     }
}

现在的问题是,当我调用包含在请求对象中的getMyObject()方法时,Jackson返回嵌套的自定义对象为LinkedHashMap。有没有办法指定需要返回T对象?例如:如果我发送了Customer类型的对象,则应该从该列表中返回Customer对象。


请添加getT()的实现。 - Jim Garrison
该问题类似于https://dev59.com/VW025IYBdhLWcg3wW0kc,但他们建议使用TypeFactory指定类型。然而,我在编译时不知道类型... - techzen
TypeFactory有一些不需要静态类的方法,例如createCollectionType等。 - StaxMan
请分享完整的代码。我也遇到了同样的问题。 - AZ_
TypeReference 不是抽象的吗? - Kyle Delaney
3个回答

265

Java类型擦除是一个众所周知的问题:T只是一个类型变量,你必须指定实际的类,通常作为Class参数。如果没有这些信息,最好使用边界;而普通的T大致相当于“T extends Object”。然后Jackson将把JSON对象绑定为Maps。

在这种情况下,测试方法需要访问Class,并且你可以构造

JavaType type = mapper.getTypeFactory().
  constructCollectionType(List.class, Foo.class)

然后

List<Foo> list = mapper.readValue(new File("input.json"), type);

27
它奏效了:我做了以下操作:JavaType topMost = mapper.getTypeFactory().constructParametricType(MyWrapper.class, ActualClassRuntime.class); 然后进行了readValue,最终它奏效了 :) - techzen
1
@StaxMan,从现在开始使用ClassMate是否更好? - husayt
2
@husayt 是的,从技术上讲,java-classmate库更优秀。但是将其与Jackson集成有点棘手,因为Jackson自己的类型抽象是API的一部分。从长远来看,找到使Jackson使用classmate代码的正确方法,无论是嵌入式还是通过依赖项,都是很好的选择。 - StaxMan
1
@MartinAndersson,为什么会呢?JSON只是数据,既没有类型(除了简单的JSON类型列表、映射、数字、字符串、布尔值),也没有身份。任何类型都是由Java代码解释的;虽然它可以使用约定来编码类型(对于多态类型必须这样做),但如果调用者可以指定将数据映射到的类型,则不需要。 - StaxMan
2
我觉得Jackson不应该去弥补泛型中的空缺,但无论如何,它做得非常好。 - Adrian Baker
显示剩余2条评论

12

'JavaType' 很好用! 我一直在尝试将json字符串中的List反序列化为ArrayList java对象,但是却苦苦寻找解决方案几天了。
以下是最终给我解决的代码。 代码:

JsonMarshallerUnmarshaller<T> {
    T targetClass;

    public ArrayList<T> unmarshal(String jsonString) {
        ObjectMapper mapper = new ObjectMapper();

        AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
        mapper.getDeserializationConfig()
            .withAnnotationIntrospector(introspector);

        mapper.getSerializationConfig()
            .withAnnotationIntrospector(introspector);
        JavaType type = mapper.getTypeFactory().
            constructCollectionType(
                ArrayList.class, 
                targetclass.getClass());

        try {
            Class c1 = this.targetclass.getClass();
            Class c2 = this.targetclass1.getClass();
            ArrayList<T> temp = (ArrayList<T>) 
                mapper.readValue(jsonString,  type);
            return temp ;
        } catch (JsonParseException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null ;
    }  
}

2
如何初始化TargetClass? - AZ_
请给我展示一个小例子。我传递了 Class<?> target,然后获取 target.getClassName()。 - AZ_
1
添加如下的构造函数:JsonMarshallerUnmarshaller{ private Class targetClass ; JsonMarshallerUnmarshaller(Class c){ targetClass = c ;
} }现在对'unmarshal'函数进行适当更改,以使用此类,而不是在各处执行getClass。
- rushidesai1
几点说明:代码可以通过注意到所有异常都是IOException的子类型(只需要一个catch)来简化,而且默认的注解内省器已经是JacksonAnnotationIntrospector,因此不需要对ObjectMapper进行任何操作,只需构造它即可。 - StaxMan
1
这段代码我甚至无法编译。你有现成的示例可以粘贴吗? - Wrench
我在下面创建了一个可工作的示例 - Mr. Polywhirl

0

我修改了 rushidesai1的答案,并加入了一个可运行的例子。

JsonMarshaller.java

import java.io.*;
import java.util.*;

public class JsonMarshaller<T> {
    private static ClassLoader loader = JsonMarshaller.class.getClassLoader();

    public static void main(String[] args) {
        try {
            JsonMarshallerUnmarshaller<Station> marshaller = new JsonMarshallerUnmarshaller<>(Station.class);
            String jsonString = read(loader.getResourceAsStream("data.json"));
            List<Station> stations = marshaller.unmarshal(jsonString);
            stations.forEach(System.out::println);
            System.out.println(marshaller.marshal(stations));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("resource")
    public static String read(InputStream ios) {
        return new Scanner(ios).useDelimiter("\\A").next(); // Read the entire file
    }
}

输出

Station [id=123, title=my title, name=my name]
Station [id=456, title=my title 2, name=my name 2]
[{"id":123,"title":"my title","name":"my name"},{"id":456,"title":"my title 2","name":"my name 2"}]

JsonMarshallerUnmarshaller.java

import java.io.*;
import java.util.List;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;

public class JsonMarshallerUnmarshaller<T> {
    private ObjectMapper mapper;
    private Class<T> targetClass;

    public JsonMarshallerUnmarshaller(Class<T> targetClass) {
        AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();

        mapper = new ObjectMapper();
        mapper.getDeserializationConfig().with(introspector);
        mapper.getSerializationConfig().with(introspector);

        this.targetClass = targetClass;
    }

    public List<T> unmarshal(String jsonString) throws JsonParseException, JsonMappingException, IOException {
        return parseList(jsonString, mapper, targetClass);
    }

    public String marshal(List<T> list) throws JsonProcessingException {
        return mapper.writeValueAsString(list);
    }

    public static <E> List<E> parseList(String str, ObjectMapper mapper, Class<E> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return mapper.readValue(str, listType(mapper, clazz));
    }

    public static <E> List<E> parseList(InputStream is, ObjectMapper mapper, Class<E> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return mapper.readValue(is, listType(mapper, clazz));
    }

    public static <E> JavaType listType(ObjectMapper mapper, Class<E> clazz) {
        return mapper.getTypeFactory().constructCollectionType(List.class, clazz);
    }
}

Station.java

public class Station {
    private long id;
    private String title;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return String.format("Station [id=%s, title=%s, name=%s]", id, title, name);
    }
}

data.json

[{
  "id": 123,
  "title": "my title",
  "name": "my name"
}, {
  "id": 456,
  "title": "my title 2",
  "name": "my name 2"
}]

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