简体   繁体   English

Jackson - 使用通用 class 反序列化

[英]Jackson - Deserialize using generic class

I have a json string, which I should deSerialize to the following class我有一个 json 字符串,我应该将其反序列化为以下 class

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

How do I do it?我该怎么做? This is the usual way这是通常的方式

mapper.readValue(jsonString, Data.class);

But how do I mention what T stands for?但是我怎么提到T代表什么?

You need to create a TypeReference object for each generic type you use and use that for deserialization.您需要为您使用的每个泛型类型创建一个TypeReference对象,并将其用于反序列化。 For example -例如 -

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

You can't do that: you must specify fully resolved type, like Data<MyType> .您不能这样做:您必须指定完全解析的类型,例如Data<MyType> T is just a variable, and as is meaningless. T只是一个变量,没有意义。

But if you mean that T will be known, just not statically, you need to create equivalent of TypeReference dynamically.但是如果你的意思是T是已知的,只是不是静态的,你需要动态地创建TypeReference等价物。 Other questions referenced may already mention this, but it should look something like:引用的其他问题可能已经提到了这一点,但它应该看起来像:

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}

First thing you do is serialize, then you can do deserialize.您要做的第一件事是序列化,然后您可以进行反序列化。
so when you do serialize, you should use @JsonTypeInfo to let jackson write class information into your json data.所以当你做序列化时,你应该使用@JsonTypeInfo让jackson将类信息写入你的json数据中。 What you can do is like this:你可以做的是这样的:

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

Then when you deserialize, you will find jackson has deserialize your data into a class which your variable hits actually is at runtime.然后当您反序列化时,您会发现 jackson 已将您的数据反序列化为您的变量在运行时实际命中的类。

From Jackson 2.5, an elegant way to solve that is using the TypeFactory.constructParametricType(Class parametrized, Class... parameterClasses) method that allows to define straigthly a Jackson JavaType by specifying the parameterized class and its parameterized types.从 Jackson 2.5 开始,一种优雅的解决方法是使用TypeFactory.constructParametricType(Class parametrized, Class... parameterClasses)方法,该方法允许通过指定参数化类及其参数化类型来直接定义 Jackson JavaType

Supposing you want to deserialize to Data<String> , you can do :假设您想反序列化为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);

Note that if the class declared multiple parameterized types, it would not be really harder :请注意,如果该类声明了多个参数化类型,则不会更难:

class Data <T, U> {
    int found;
    Class<T> hits;
    List<U> list;
}

We could do :我们可以这样做:

JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);

For class Data<>对于类数据<>

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

Just write a static method in Util class.只需在 Util 类中编写一个静态方法。 I am reading a Json from a file.我正在从文件中读取 Json。 you can give String also to readValue你也可以给 String 给 readValue

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())));
}

Usage:用法:

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

You can wrap it in another class which knows the type of your generic type.您可以将它包装在另一个知道泛型类型的类中。

Eg,例如,

class Wrapper {
 private Data<Something> data;
}
mapper.readValue(jsonString, Wrapper.class);

Here Something is a concrete type.这里Something是一个具体的类型。 You need a wrapper per reified type.每个具体类型都需要一个包装器。 Otherwise Jackson does not know what objects to create.否则杰克逊不知道要创建什么对象。

JSON string that needs to be deserialized will have to contain the type information about parameter T .需要反序列化的 JSON 字符串必须包含有关参数T的类型信息。
You will have to put Jackson annotations on every class that can be passed as parameter T to class Data so that the type information about parameter type T can be read from / written to JSON string by Jackson.您必须将 Jackson 注释放在每个可以作为参数T传递给类Data类上,以便 Jackson 可以从 JSON 字符串读取/写入有关参数类型T的类型信息。

Let us assume that T can be any class that extends abstract class Result .让我们假设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 {

}

Once each of the class (or their common supertype) that can be passed as parameter T is annotated, Jackson will include information about parameter T in the JSON.一旦注释了可以作为参数T传递的每个类(或其公共超类型),Jackson 将在 JSON 中包含有关参数T信息。 Such JSON can then be deserialized without knowing the parameter T at compile time.然后可以在编译时不知道参数T情况下反序列化此类 JSON。
This Jackson documentation link talks about Polymorphic Deserialization but is useful to refer to for this question as well.这个Jackson 文档链接讨论了多态反序列化,但对于这个问题也很有用。

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;
    }
    ...
 }

if you're using scala and know the generic type at compile time, but don't want to manually pass TypeReference everywhere in all your api l ayers, you can use the following code (with jackson 2.9.5):如果您使用 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)
  }

which can be used like this:可以这样使用:

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

To deserialize a generic JSON-string to Java-object with Jackson you need:要使用 Jackson 将通用 JSON 字符串反序列化为 Java 对象,您需要:

  1. To define a JSON class.定义一个 JSON 类。

  2. Perform an attributes mapping.执行属性映射。

Final code, tested, and ready-to-be used:最终代码,经过测试,可以使用:

static class MyJSON {

    private Map<String, Object> content = new HashMap<>();

    @JsonAnySetter
    public void setContent(String key, Object value) {
        content.put(key, value);
    }
}

String json = "{\"City\":\"Prague\"}";

try {

    MyPOJO myPOJO = objectMapper.readValue(json, MyPOJO.class);

    String jsonAttVal = myPOJO.content.get("City").toString();

    System.out.println(jsonAttVal);

} catch (IOException e) {
    e.printStackTrace();
}

Important:重要的:
@JsonAnySetter annotation is mandatory, it ensures a generic JSON-parsing and population. @JsonAnySetter注释是强制性的,它确保通用的 JSON 解析和填充。

For more complicated cases with nested arrays please see the Baeldung reference: https://www.baeldung.com/jackson-mapping-dynamic-object有关嵌套数组的更复杂情况,请参阅 Baeldung 参考: https ://www.baeldung.com/jackson-mapping-dynamic-object

Example of not very good, but simple decision (not only for Jackson, also for Spring RestTemplate, etc.):不是很好但很简单的决定示例(不仅适用于 Jackson,也适用于 Spring RestTemplate 等):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM