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:
To define a JSON class.
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);
Source: Stackoverflow.com