Class type of generic variable - java

I have data structure that looks more or less like this
class ResponseWrapper<T> {
T response;
public ResponseWrapper(T response) {
this.response = response;
}
}
And service that handles reading that response from JSON to actual DTO.
public class GenericService<T> {
public ResponseWrapper<T> read(String json, Class<T> clazz) throws Exception {
T response = new ObjectMapper().readValue(json, clazz);
return new ResponseWrapper<>(response);
}
}
And I can call it like this:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value", SomeData.class)
And what I'm trying to achieve is:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value")
And I'm wondering, is it actually possible? This is obviously not working
public ResponseWrapper<T> read(String json) throws Exception {
T response = new ObjectMapper().readValue(json, T.class);
return new ResponseWrapper<>(response);
}

No. It is not possible.
Java generics work by type erasure ... and that means that the actual class associated with generic type parameter is not available at runtime. If your code needs to know that class, you need to pass a Class object explicitly.
And, yes, T.class is a compilation error.
And, yes, there is no way to get the class of T.

Related

Deserialise using java generics

Consider this External execution class
public class ExternalCommands {
private Logger log = LoggerFactory.getLogger(ExternalCommands.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
public <T> CustomResponse<T> executeQuery(Clients client, Query query, Class<T> classType) throws Exception {
if (Objects.isNull(clients))
throw new Exception("external client is null in external commands");
log.debug("Query : {}", query);
Response queryResponse = clients.getElasticClient().executeQuery(query);
log.debug("query response : {}", queryResponse);
if (queryResponse.status() == 200) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(CustomResponse.class, classType); // This is the coding sin m talking about
return objectMapper.readValue(queryResponse.body().asInputStream(), javaType);
}
}
}
So Basically this executeQuery function fetches all the data as per query from an elastic client and deserialise it as per the generic classType as sent in function BUT
this is more like explicitly doing the deserialisation instead of using Generics.
See below code on how this execution works along with inline comments :
public ArrayList<EmpData> getEmpData() throws Exception {
ArrayList<EmpData> merchantUrnMap = new ArrayList<>();
List<Filter> filters = new ArrayList<>();
filters.add("Added filter 1 here");
filters.add("Added filter 2 here");
filters.add("Added filter 3 here");
ExternalCommands commands = new ExternalCommands();
Query query = commands.getQuery(filters);
// get "clients"
// this is how it works now
CustomResponse<EmpData> response = commands.executeQuery(clients, query, EmpData.class);
// this is how i WANT IT TO WORK - without passing "EmpData.class"
// but in this case <T> in "CustomResponse<T>" would not deserialise to "EmpData"
// resulting in LinkedHashMap in deseralised object instead of class object
// CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
// some operations
return response
}
any suggestions on how to achieve this?
Case 1: assuming that constructParametricType requires the classType argument to function properly, and you can't change the implementation of methods/classes that are implied but not provided in your posted code.
Your proposed method signature/invocation not possible due to type erasure in Java.
You use classType in the constructParametricType(CustomResponse.class, classType) call, and you're trying to replace classType with T somehow. This is impossible, because when the code is compiled, the T is erased completely. There is no way to do something like constructParametricType(CustomResponse.class, T.class) because T doesn't exist at runtime.
The correct solution is to pass in the class as a method argument, which is precisely what your existing approach does.
Case 2: you really want to have the call commands.<EmpData>executeQuery(clients, query); and you're willing to change anything to achieve that goal.
Since we cannot pass T as an argument to constructParametricType, it must be called as constructParametricType(CustomResponse.class), yet it needs to return a JavaType representing CustomResponse<T>. The only way to do that is to declare
<T> JavaType<T> constructParametricType(Class<?> cls)
Note that JavaType now also has to be parameterized for the same reason (we can't get T at runtime). Finally, we have to declare
CustomResponse<T> readValue(InputStream stream, JavaType<T> javaType)
to match the declared return type of executeQuery.
After all of these changes, the line
CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
should compile. Here's a minimal example:
class CustomResponse<T> {}
class Clients{}
class Query{}
class EmpData{}
class ObjectMapper {
JavaTypeFactory getTypeFactory() {
return new JavaTypeFactory();
}
<T> CustomResponse<T> readValue(InputStream s, JavaType<T> j) {
return new CustomResponse<>();
}
}
class JavaTypeFactory {
<T> JavaType<T> constructParametricType(Class<?> cls) {
return new JavaType<>(cls);
}
}
class JavaType<T> {
JavaType(Class<?> cls) {}
}
class ExternalCommands {
private static final ObjectMapper objectMapper = new ObjectMapper();
public <T> CustomResponse<T> executeQuery(Clients clients, Query query) throws Exception {
InputStream queryResponseStream = null;
JavaType<T> javaType = objectMapper.getTypeFactory().<T>constructParametricType(CustomResponse.class);
return objectMapper.readValue(queryResponseStream, javaType);
}
}
class SomeClass {
public void getEmpData() throws Exception {
ExternalCommands commands = new ExternalCommands();
Query query = null;
Clients clients = null;
CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
}
}
Beware that some of the described changes might not be easy/possible given the rest of your system (especially parameterizing JavaType), and I don't recommend this approach. I recommend sticking with what you have; it's the cleanest approach IMO.

How to call parametrized method in Java?

I have such a method:
private static <T extends HomerMessage> HomerMessage postRequest(String path, HomerMessage json) throws IOException, HomerDoh
{
RequestBody body = RequestBody.create(JSON, toJson(json));
Request request = new Request.Builder().url("http://" + path).post(body).build();
String response = new OkHttpClient().newCall(request).execute().body().string();
System.out.println(response);
JsonNode responseNode = new ObjectMapper().readValue(response, JsonNode.class);
if(!"200".equals(responseNode.get("status")))
{
throw readData(response, new TypeReference<HomerDoh>() {});
}
return readData(response, new TypeReference<T>() {});
}
private static <T> T readData(String is, TypeReference<T> ref) throws IOException
{
return mapper.readValue(is, ref);
}
All works fine, but I could not figure out how to call it...
I have tried:
AuthResponse ar = HomerClient.postRequest(url + "/api/v1/session", auth);
The last expression does not compile.
How to call parametrized method in Java?
AuthResponse extends HomerMessage
Your code doesn't compile because it's not returning an AuthResponse: it is returning a HomerMessage.
You can make the return type AuthResponse if you change the return type of the method to:
private static <T extends HomerMessage> T postRequest(
String path, HomerMessage json, TypeReference<T> typeRef)
throws IOException, HomerDoh
which is the return type of your only normally-completing code path.
As noted by #SLaks, you can't use TypeReference with generics:
new TypeReference<T>() {}
Because of erasure, this will be equivalent at runtime to:
new TypeReference<Object>() {}
which is almost certainly not what you want - otherwise you could just have used that, and not had an issue calling the generic method in the first place.
You need to actually pass in the concrete TypeReference as a parameter:
private static <T extends HomerMessage> T postRequest(
String path, HomerMessage json, TypeReference<T> typeRef)
throws IOException, HomerDoh
then you can call this simply as:
AuthResponse response = postRequest(
url + "/api/v1/session", auth,
new TypeReference<AuthResponse>() {});
and the type T is inferred from the third parameter.
You need to provide the name of the class, where the static method is defined:
Exception ar = SomeClass.<Exception>getException();
The expression Exception ar = <Exception>getException(); cannot compile because the generic method parametrization idiom requires a class name (or variable name if the method wasn't static) prior to parametrization.
For instance:
Exception ar = MyClass.<Exception>getException();
For instance methods:
Exception ar = theObject.<Exception>getException();
Exception ar = this.<Exception>getException();

Jackson Parser To Java Object

I am using Jackson to parse object. sometime I need list of objects.
when I am using like this its working
List<MyObject> mapper.readValue(file , new TypeReference<MyObject>() {})
but when I am using it like this its not working
public class JsonMocksRepository<T>{
public T getObject() throws Exception{
return mapper.readValue(file ,new TypeReference<T>());
}
}
What I need to do ?
Basically I want to use generics to get the right class
This is because of type erasure. There is no information about the actual type represented by T available at runtime, so your TypeReference will be effectively be simply TypeReference<Object>.
If you want a generic instance of JsonMocksRepository, you will need to inject the TypeReference at construction time:
public class JsonMocksRepository<T>{
private final TypeReference<T> typeRef;
public JsonMocksRepository(TypeReference<T> typeRef) {
this.typeRef = typeRef;
}
public T getObject() throws Exception{
return mapper.readValue(file, typeRef);
}
}

De-serializing nested, generic class with gson

Using Gson, I'm trying to de-serialize a a nested, generic class. The class structure looks like the following:
Wrapper object, simplified, but normally holds other properties such as statusMessage, which are returned along with the data-field from the server:
public class Response<T> {
private List<T> data = null;
public List<T> getData() { return this.data; }
}
Simple class, the expected output from data-field above (though as an array):
public class Language {
public String alias;
public String label;
}
Usage:
Type type = new TypeToken<Response<Language>>() {}.getType();
Response<Language> response = new Gson().fromJson(json, type);
List<Language> languages = response.getData();
Language l = languages.get(0);
System.out.println(l.alias); // Error occurs here
Where the json-variable is something like this.
However, when doing this, I recieve the following exception (on line 3, last code example):
ClassCastException: com.google.gson.internal.StringMap cannot be cast to book.Language
The exception ONLY occurs when storing the data from getData() into a variable (or when used as one).
Any help would be highly appreciated.
The problem you're actually having is not directly due to Gson, it's because of how arrays and Generics play together.
You'll find that you can't actually do new T[10] in a class like yours. see: How to create a generic array in Java?
You basically have two options:
Write a custom deserializer and construct the T[] array there as shown in the SO question I linked above
Use a List<T> instead, then it will simply work. If you really need to return an array, you can always just call List.toArray() in your method.
Edited from comments below:
This is a fully working example:
public class App
{
public static void main( String[] args )
{
String json = "{\"data\": [{\"alias\": \"be\",\"label\": \"vitryska\"},{\"alias\": \"vi\",\"label\": \"vietnamesiska\"},{\"alias\": \"hu\",\"label\": \"ungerska\"},{\"alias\": \"uk\",\"label\": \"ukrainska\"}]}";
Type type = new TypeToken<Response<Language>>(){}.getType();
Response<Language> resp = new Gson().fromJson(json, type);
Language l = resp.getData().get(0);
System.out.println(l.alias);
}
}
class Response<T> {
private List<T> data = null;
public List<T> getData() { return this.data; }
}
class Language {
public String alias;
public String label;
}
Output:
be

Obtaining types through parameters on Java

Certainly I'm quite new in all this Java stuff, so I have a question, I'm trying to deserialize a response obtained on a WCF service, everything works fine, but, I'm trying to make a generic function to do this.
Basically what I do is
public List<msg> GetService(String method){
List<msg> response = new ArrayList<msg>();
Type msgType = new TypeToken<List<msg>>(){}.getType();
//Obtaining result
response = uJSON.fromJson(serviceResponse, msgType);
//uJSON is an instance of Gson library, for deserializing it just needs
//the service response and a Class<T> or Type to reflect the obtained message
}
What I'm trying to do is obtaining the Type "msg" generic, it means...
public <thing> void GetInstanceService(String method){
List<thing> response = new ArrayList<thing>();
Type rType2 = new TypeToken<List<thing>>(){}.getType(); //Got java.util.List<thing>
//And when I'm trying to deserialize I just obtain a List of object
//[java.lang.Object#5c7a987e, java.lang.Object#74b1a7a0]
type2 = uJSON.fromJson(new String(entity), rType2);
}
But I'm calling like this.
comm.<msgType>GetInstanceService("listTestType");
So, when I call "GetInstanceService", "thing" is "msgType" Type, for the
List<thing> and also response shouldn't be List<msgType> instead of List <Object>?
Besides, when I'm trying to explicitly pass the type through a "Type" parameter, it just causes me compilation time error like this.
public void GetInstanceService(Type type){
List<type> type2 = new ArrayList<type>(); //Compilation time error
//Or
msgType oType = new msgType();
Class classType = oType.getClass();
List<classType> type3; //Compilation time error
}
So, if none of these attempts was effective, how could I set the type for deserialization?
Guava class TypeToken does not support that mode of usage. You are creating the type token with a type variable and there not enough information for it to reconstruct List<String> from List<T>. You should create an instance of TypeToken where you have all the required compile-time information.
The documentation says:
Note that it's critical that the actual type argument is carried by a
subclass. The following code is wrong because it only captures the <T>
type variable of the listType() method signature; while <String> is
lost in erasure:
class Util {
static <T> TypeToken<List<T>> listType() {
return new TypeToken<List<T>>() {};
}
}
TypeToken<List<String>> stringListType = Util.<String>listType();
But as said above, you can instantiate the TypeToken at call-site, where all type info are available, and then pass it as a parameter. Something like this:
public <thing> void GetInstanceService(String method, TypeToken<List<thing>> token){
List<thing> response = new ArrayList<thing>();
Type rType2 = token.getType();
type2 = uJSON.fromJson(new String(entity), rType2);
}
comm.GetInstanceService("listTestType", new TypeToken<List<msgType>>() {});
Update
Paul Bellora notes that you can also accept a parameter TypeToken<thing> token, and construct a TypeToken<List<thing>> inside the method from that token:
public <thing> void GetInstanceService(String method, TypeToken<thing> token) {
List<thing> response = new ArrayList<thing>();
Type rType2 = new TypeToken<List<thing>>() {}
.where(new TypeParameter<thing>() {}, token); // where() binds "thing" to token
.getType();
type2 = uJSON.fromJson(new String(entity), rType2);
}
comm.GetInstanceService("listTestType", new TypeToken<msgType>() {});
Due to something called type erasure, the class object you need is not available at runtime.
However, there is a standard work-around: pass a type token into your method, like this:
public <T> List<T> getService(String method, Class<T> c) {
// the caller has passed in the class object
List<T> list = new ArrayList<T>();
// fill list
return list;
}

Categories

Resources