Jackson - Deserialize using generic class

JavaJsonGenericsJackson

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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

Solution 2 - Java

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

Solution 3 - Java

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.

Solution 4 - Java

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

Solution 5 - Java

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)


   

Solution 6 - Java

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

Solution 7 - Java

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.

Solution 8 - Java

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.

Solution 9 - Java

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

Solution 10 - Java

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongnjagoView Question on Stackoverflow
Solution 1 - JavaEser AygünView Answer on Stackoverflow
Solution 2 - JavaStaxManView Answer on Stackoverflow
Solution 3 - JavaCharlieQView Answer on Stackoverflow
Solution 4 - JavadavidxxxView Answer on Stackoverflow
Solution 5 - JavaDeveshView Answer on Stackoverflow
Solution 6 - JavaAZ_View Answer on Stackoverflow
Solution 7 - JavasksamuelView Answer on Stackoverflow
Solution 8 - JavaSanjeev SachdevView Answer on Stackoverflow
Solution 9 - Javamikka22View Answer on Stackoverflow
Solution 10 - Javanathan gView Answer on Stackoverflow