使用通用类进行Jackson反序列化

241
我有一个json字符串,需要将其反序列化为以下类:
class Data <T> {
    int found;
    Class<T> hits
}

我该怎么做呢? 这是通常的方式。

mapper.readValue(jsonString, Data.class);

但是我该如何提及 T 代表什么?


1
可能是Jackson - Deserialize Generic class variable的重复问题。 - Programmer Bruce
请参见以下链接:https://dev59.com/zWQm5IYBdhLWcg3wtAw_ - lisak
10个回答

400

您需要为每个使用的泛型类型创建一个TypeReference对象,并在反序列化时使用它。例如 -

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});

1
我必须使用TypeReference<Data<T>>(){},但是我遇到了以下错误 - 无法访问java.lang.class.Class的私有构造函数。设置访问权限失败。无法使java.lang.Class构造函数可访问。 - gnjago
41
如果我直到运行时才知道它是哪个类,该怎么办?我将在运行时将类作为参数获取。代码示例如下: public <T> void deSerialize(Class<T> clazz { ObjectMapper mapper = new ObjectMapper(); mapper.readValue(jsonString, new TypeReference<Json<T>>() {}); } - gnjago
3
我已经在这里正确地提出了完整的问题:https://dev59.com/L2gu5IYBdhLWcg3wASaO - gnjago
3
TypeReference的完整包名是什么?是com.fasterxml.jackson.core.type吗? - Lei Yang
5
如果你的左侧赋值引用已有类型,就不需要定义TypeReference类型,只需使用以下代码:mapper.readValue(jsonString, new TypeReference<>() {}); - Zon
显示剩余2条评论

127
您不能这样做:您必须指定完全解析的类型,例如Data<MyType>T只是一个变量,因此没有意义。
但是,如果您的意思是T会被知道,只是不是静态的,那么您需要动态创建TypeReference的等效物。其他相关问题可能已经提到了这一点,但它应该看起来像:
public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}

3
如果我在运行时不知道类是什么,该怎么办?我会在运行时将类作为参数获取。代码如下: public <T> void deSerialize(Class<T> clazz { ObjectMapper mapper = new ObjectMapper(); mapper.readValue(jsonString, new TypeReference<Json<T>>() {}); } - gnjago
3
直接按原样通过此类,不需要使用TypeReferencereturn mapper.readValue(json, clazz); 这里到底有什么问题? - StaxMan
2
问题在于“Data”是一个通用类。我需要在运行时指定T的类型。参数clazz就是在运行时T的类型。那么,如何调用readValue?使用new TypeReference>Json<T>>调用它不起作用。完整的问题在这里:https://dev59.com/L2gu5IYBdhLWcg3wASaO - gnjago
2
好的。那么你需要使用 TypeFactory.. 我会编辑我的回答。 - StaxMan
4
从 Jackson 2.5 开始,应使用 TypeFactory.constructParametrizedType,因为 TypeFactory.constructParametricType 已被弃用。 - David V
显示剩余6条评论

40

首先要做的是序列化,然后才能进行反序列化。
因此,在进行序列化时,您应该使用 @JsonTypeInfo 将类信息写入您的 json 数据中。您可以像这样操作:

Class Data <T> {
    int found;
    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    Class<T> hits
}

当你反序列化时,你会发现Jackson已将你的数据反序列化为运行时变量hits所在的类。


无法工作,出现以下错误 com.fasterxml.jackson.databind.exc.InvalidTypeIdException: 尝试解析[简单类型,类java.lang.Object]的子类型时缺少类型ID:缺少类型ID属性'@class'(用于POJO属性'data') - gaurav9620

31

从Jackson 2.5开始,解决这个问题的一种优雅方法是使用TypeFactory.constructParametricType(Class parametrized, Class... parameterClasses) 方法来定义一个Jackson JavaType,直接指定参数化类和其参数化类型。

如果你想反序列化为 Data<String>,可以这样做:

// the json variable may be a String, an InputStream and so for...
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class);
Data<String> data = mapper.readValue(json, type);

请注意,如果类声明了多个参数化类型,则实际上并不会更难:
class Data <T, U> {
    int found;
    Class<T> hits;
    List<U> list;
}

我们可以做到以下几点:
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);

太棒了,谢谢,这对我有用。使用typereference时,我从映射到特定对象中得到了类转换异常,但这确实完成了工作。 - Tacsiazuma

16

对于Data类<>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)

1
这个现在已经被弃用了。 - Evan Gertis

15

在Util类中编写一个静态方法。我正在从文件中读取Json。您也可以提供字符串以读取值。

public static <T> T convertJsonToPOJO(String filePath, Class<?> target) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(new File(filePath), objectMapper .getTypeFactory().constructCollectionType(List.class, Class.forName(target.getName())));
}

使用方法:

List<TaskBean> list =  Util.<List<TaskBean>>convertJsonToPOJO("E:/J2eeWorkspaces/az_workspace_svn/az-client-service/dir1/dir2/filename.json", TaskBean.class);

7

需要反序列化的JSON字符串必须包含有关参数T的类型信息。
您需要在可以作为参数T传递给类Data的每个类上放置Jackson注释,以便Jackson可以从/写入JSON字符串中读取有关参数类型T的类型信息。

假设T可以是扩展抽象类Result的任何类。

class Data <T extends Result> {
    int found;
    Class<T> hits
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ImageResult.class, name = "ImageResult"),
        @JsonSubTypes.Type(value = NewsResult.class, name = "NewsResult")})
public abstract class Result {

}

public class ImageResult extends Result {

}

public class NewsResult extends Result {

}

一旦类(或它们的公共超类型)被标记为可传递给参数T,Jackson就会在JSON中包含有关参数T的信息。这样的JSON可以在不知道编译时参数T的情况下进行反序列化。
这个Jackson文档链接讨论了多态反序列化,但也对此问题有用。

如果我想要一个列表,那么我该如何管理它呢?比如说 List<ImageResult>。 - Luca Archidiacono

7
您可以将其包裹在另一个类中,该类知道您通用类型的类型。
例如,
class Wrapper {
 private Data<Something> data;
}
mapper.readValue(jsonString, Wrapper.class);

这里的"Something"是一个具体的类型。你需要为每个具体化的类型提供一个包装器,否则Jackson不知道要创建哪些对象。


1
public class Data<T> extends JsonDeserializer implements ContextualDeserializer {
    private Class<T> cls;
    public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty prop) throws JsonMappingException {
        cls = (Class<T>) ctx.getContextualType().getRawClass();
        return this;
    }
    ...
 }

-1
如果您正在使用Scala并且在编译时知道通用类型,但不想在所有API层中手动传递TypeReference,则可以使用以下代码(使用jackson 2.9.5):
def read[T](entityStream: InputStream)(implicit typeTag: WeakTypeTag[T]): T = {

    //nathang: all of this *crazy* scala reflection allows us to handle List[Seq[Map[Int,Value]]]] without passing
    // new TypeReference[List[Seq[Map[Int,Value]]]]](){} to the function

    def recursiveFindGenericClasses(t: Type): JavaType = {
      val current = typeTag.mirror.runtimeClass(t)

      if (t.typeArgs.isEmpty) {
        val noSubtypes = Seq.empty[Class[_]]
        factory.constructParametricType(current, noSubtypes:_*)
      }

      else {
        val genericSubtypes: Seq[JavaType] = t.typeArgs.map(recursiveFindGenericClasses)
        factory.constructParametricType(current, genericSubtypes:_*)
      }

    }

    val javaType = recursiveFindGenericClasses(typeTag.tpe)

    json.readValue[T](entityStream, javaType)
  }

可以像这样使用:

read[List[Map[Int, SomethingToSerialize]]](inputStream)

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