[java] Jackson - Deserialize using generic class

I have a json string, which I should deSerialize to the following 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?

This question is related to java json generics jackson

The answer is


JSON string that needs to be deserialized will have to contain the type information about parameter 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.

Let us assume that T can be any class that extends abstract class 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. Such JSON can then be deserialized without knowing the parameter T at compile time.
This Jackson documentation link talks about Polymorphic Deserialization but is useful to refer to for this question as well.


You need to create a TypeReference object for each generic type you use and use that for deserialization. For example -

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

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):

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)

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


You can't do that: you must specify fully resolved type, like Data<MyType>. T is just a variable, and as is meaningless.

But if you mean that T will be known, just not statically, you need to create equivalent of TypeReference dynamically. 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);
}

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)

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

To deserialize a generic JSON-string to Java-object with Jackson you need:

  1. To define a JSON class.

  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.

For more complicated cases with nested arrays please see the Baeldung reference: 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.):

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

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. You need a wrapper per reified type. Otherwise Jackson does not know what objects to create.


Just write a static method in Util class. I am reading a Json from a file. you can give String also to 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);

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.

Supposing you want to deserialize to Data<String>, you can do :

// 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);

Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to json

Use NSInteger as array index Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) HTTP POST with Json on Body - Flutter/Dart Importing json file in TypeScript json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 190) Angular 5 Service to read local .json file How to import JSON File into a TypeScript file? Use Async/Await with Axios in React.js Uncaught SyntaxError: Unexpected token u in JSON at position 0 how to remove json object key and value.?

Examples related to generics

Instantiating a generic type Are these methods thread safe? The given key was not present in the dictionary. Which key? Using Java generics for JPA findAll() query with WHERE clause Using Spring RestTemplate in generic method with generic parameter How to create a generic array? Create a List of primitive int? How to have Java method return generic list of any type? Create a new object from type parameter in generic class What is the "proper" way to cast Hibernate Query.list() to List<Type>?

Examples related to jackson

No Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value How to convert JSON string into List of Java object? Deserialize Java 8 LocalDateTime with JacksonMapper java.lang.IllegalArgumentException: No converter found for return value of type java.lang.ClassNotFoundException: com.fasterxml.jackson.annotation.JsonInclude$Value How to modify JsonNode in Java? Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error Convert Map to JSON using Jackson Required request body content is missing: org.springframework.web.method.HandlerMethod$HandlerMethodParameter