To use generic types with Spring RestTemplate we need to use ParameterizedTypeReference (Unable to get a generic ResponseEntity<T> where T is a generic class "SomeClass<SomeGenericType>")
Suppose I have some class
public class MyClass {
int users[];
public int[] getUsers() { return users; }
public void setUsers(int[] users) {this.users = users;}
}
And some wrapper class
public class ResponseWrapper <T> {
T response;
public T getResponse () { return response; }
public void setResponse(T response) {this.response = response;}
}
So if I'm trying to do something like this, all is OK.
public ResponseWrapper<MyClass> makeRequest(URI uri) {
ResponseEntity<ResponseWrapper<MyClass>> response = template.exchange(
uri,
HttpMethod.POST,
null,
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {});
return response;
}
But when I'm trying to create generic variant of the above method ...
public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) {
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
new ParameterizedTypeReference<ResponseWrapper<T>>() {});
return response;
}
... and calling this method like so ...
makeRequest(uri, MyClass.class)
... instead of getting ResponseEntity<ResponseWrapper<MyClass>> object I'm getting ResponseEntity<ResponseWrapper<LinkedHashSet>> object.
How can I solve this problem? Is it a RestTemplate bug?
UPDATE 1
Thanks to #Sotirios I understand the concept. Unfortunately I'm newly registered here so I cant comment on his answer, so writing it here. Im not sure that I clearly understand how to implement the proposed approach to solve my problem with Map with Class key (Proposed by #Sotirios in the end of his answer). Would someone mind to give an example?
No, it is not a bug. It is a result of how the ParameterizedTypeReference hack works.
If you look at its implementation, it uses Class#getGenericSuperclass() which states
Returns the Type representing the direct superclass of the entity
(class, interface, primitive type or void) represented by this Class.
If the superclass is a parameterized type, the Type object returned
must accurately reflect the actual type parameters used in the source
code.
So, if you use
new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}
it will accurately return a Type for ResponseWrapper<MyClass>.
If you use
new ParameterizedTypeReference<ResponseWrapper<T>>() {}
it will accurately return a Type for ResponseWrapper<T> because that is how it appears in the source code.
When Spring sees T, which is actually a TypeVariable object, it doesn't know the type to use, so it uses its default.
You cannot use ParameterizedTypeReference the way you are proposing, making it generic in the sense of accepting any type. Consider writing a Map with key Class mapped to a predefined ParameterizedTypeReference for that class.
You can subclass ParameterizedTypeReference and override its getType method to return an appropriately created ParameterizedType, as suggested by IonSpin.
I am using org.springframework.core.ResolvableType for a ListResultEntity :
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(ListResultEntity.class, itemClass);
ParameterizedTypeReference<ListResultEntity<T>> typeRef = ParameterizedTypeReference.forType(resolvableType.getType());
So in your case:
public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) {
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
ParameterizedTypeReference.forType(ResolvableType.forClassWithGenerics(ResponseWrapper.class, clazz)));
return response;
}
This only makes use of spring and of course requires some knowledge about the returned types (but should even work for things like Wrapper>> as long as you provide the classes as varargs )
As the code below shows it, it works.
public <T> ResponseWrapper<T> makeRequest(URI uri, final Class<T> clazz) {
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
new ParameterizedTypeReference<ResponseWrapper<T>>() {
public Type getType() {
return new MyParameterizedTypeImpl((ParameterizedType) super.getType(), new Type[] {clazz});
}
});
return response;
}
public class MyParameterizedTypeImpl implements ParameterizedType {
private ParameterizedType delegate;
private Type[] actualTypeArguments;
MyParameterizedTypeImpl(ParameterizedType delegate, Type[] actualTypeArguments) {
this.delegate = delegate;
this.actualTypeArguments = actualTypeArguments;
}
#Override
public Type[] getActualTypeArguments() {
return actualTypeArguments;
}
#Override
public Type getRawType() {
return delegate.getRawType();
}
#Override
public Type getOwnerType() {
return delegate.getOwnerType();
}
}
As Sotirios explains, you can not use the ParameterizedTypeReference, but ParameterizedTypeReference is used only to provide Type to the object mapper, and as you have the class that is removed when type erasure happens, you can create your own ParameterizedType and pass that to RestTemplate, so that the object mapper can reconstruct the object you need.
First you need to have the ParameterizedType interface implemented, you can find an implementation in Google Gson project here.
Once you add the implementation to your project, you can extend the abstract ParameterizedTypeReference like this:
class FakeParameterizedTypeReference<T> extends ParameterizedTypeReference<T> {
#Override
public Type getType() {
Type [] responseWrapperActualTypes = {MyClass.class};
ParameterizedType responseWrapperType = new ParameterizedTypeImpl(
ResponseWrapper.class,
responseWrapperActualTypes,
null
);
return responseWrapperType;
}
}
And then you can pass that to your exchange function:
template.exchange(
uri,
HttpMethod.POST,
null,
new FakeParameterizedTypeReference<ResponseWrapper<T>>());
With all the type information present object mapper will properly construct your ResponseWrapper<MyClass> object
Actually, you can do this, but with additional code.
There is Guava equivalent of ParameterizedTypeReference and it's called TypeToken.
Guava's class is much more powerful then Spring's equivalent.
You can compose the TypeTokens as you wish.
For example:
static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) {
return new TypeToken<Map<K, V>>() {}
.where(new TypeParameter<K>() {}, keyToken)
.where(new TypeParameter<V>() {}, valueToken);
}
If you call mapToken(TypeToken.of(String.class), TypeToken.of(BigInteger.class)); you will create TypeToken<Map<String, BigInteger>>!
The only disadvantage here is that many Spring APIs require ParameterizedTypeReference and not TypeToken. But we can create ParameterizedTypeReference implementation which is adapter to TypeToken itself.
import com.google.common.reflect.TypeToken;
import org.springframework.core.ParameterizedTypeReference;
import java.lang.reflect.Type;
public class ParameterizedTypeReferenceBuilder {
public static <T> ParameterizedTypeReference<T> fromTypeToken(TypeToken<T> typeToken) {
return new TypeTokenParameterizedTypeReference<>(typeToken);
}
private static class TypeTokenParameterizedTypeReference<T> extends ParameterizedTypeReference<T> {
private final Type type;
private TypeTokenParameterizedTypeReference(TypeToken<T> typeToken) {
this.type = typeToken.getType();
}
#Override
public Type getType() {
return type;
}
#Override
public boolean equals(Object obj) {
return (this == obj || (obj instanceof ParameterizedTypeReference &&
this.type.equals(((ParameterizedTypeReference<?>) obj).getType())));
}
#Override
public int hashCode() {
return this.type.hashCode();
}
#Override
public String toString() {
return "ParameterizedTypeReference<" + this.type + ">";
}
}
}
Then you can use it like this:
public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) {
ParameterizedTypeReference<ResponseWrapper<T>> responseTypeRef =
ParameterizedTypeReferenceBuilder.fromTypeToken(
new TypeToken<ResponseWrapper<T>>() {}
.where(new TypeParameter<T>() {}, clazz));
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
responseTypeRef);
return response;
}
And call it like:
ResponseWrapper<MyClass> result = makeRequest(uri, MyClass.class);
And the response body will be correctly deserialized as ResponseWrapper<MyClass>!
You can even use more complex types if you rewrite your generic request method (or overload it) like this:
public <T> ResponseWrapper<T> makeRequest(URI uri, TypeToken<T> resultTypeToken) {
ParameterizedTypeReference<ResponseWrapper<T>> responseTypeRef =
ParameterizedTypeReferenceBuilder.fromTypeToken(
new TypeToken<ResponseWrapper<T>>() {}
.where(new TypeParameter<T>() {}, resultTypeToken));
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
responseTypeRef);
return response;
}
This way T can be complex type, like List<MyClass>.
And call it like:
ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() {});
I have another way to do this... suppose you swap out your message converter to String for your RestTemplate, then you can receive raw JSON. Using the raw JSON, you can then map it into your Generic Collection using a Jackson Object Mapper. Here's how:
Swap out the message converter:
List<HttpMessageConverter<?>> oldConverters = new ArrayList<HttpMessageConverter<?>>();
oldConverters.addAll(template.getMessageConverters());
List<HttpMessageConverter<?>> stringConverter = new ArrayList<HttpMessageConverter<?>>();
stringConverter.add(new StringHttpMessageConverter());
template.setMessageConverters(stringConverter);
Then get your JSON response like this:
ResponseEntity<String> response = template.exchange(uri, HttpMethod.GET, null, String.class);
Process the response like this:
String body = null;
List<T> result = new ArrayList<T>();
ObjectMapper mapper = new ObjectMapper();
if (response.hasBody()) {
body = items.getBody();
try {
result = mapper.readValue(body, mapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (Exception e) {
e.printStackTrace();
} finally {
template.setMessageConverters(oldConverters);
}
...
I find this to be a more elegant solution:
private static <T> ParameterizedTypeReference<BaseResponse<T>> typeReferenceOf ( Class<T> tClass ) {
return ParameterizedTypeReference.forType( sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make( BaseResponse.class, new Type[]{ tClass }, null ) );
}
For example, given the following BaseResponse and ResponseData classes:
#Getter
#Setter
public static class BaseResponse<T> {
private ResponseData<T> response;
public BaseResponse () { }
public boolean hasData () {
return response != null;
}
public T data () {
return response.data;
}
}
#Getter
#Setter
public static final class ResponseData<T> {
private T data;
public ResponseData () { }
}
And given a sample get method, using WebClient:
public <T> Mono <T> get ( URI uri, Class<T> tClass ) {
return webClient
.get ()
.uri ( uriBuilder -> uriBuilder.pathSegment( uri.getPath() ).build() )
.exchangeToMono ( clientResponse -> clientResponse.bodyToMono( typeReferenceOf( tClass ) ) )
.flatMap ( baseResponse -> baseResponse.hasData() ? Mono.just( baseResponse.data() ) : Mono.empty() );
}
Note: This answer refers/adds to Sotirios Delimanolis's answer and comment.
I tried to get it to work with Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>, as indicated in Sotirios's comment, but couldn't without an example.
In the end, I dropped the wildcard and parametrisation from ParameterizedTypeReference and used raw types instead, like so
Map<Class<?>, ParameterizedTypeReference> typeReferences = new HashMap<>();
typeReferences.put(MyClass1.class, new ParameterizedTypeReference<ResponseWrapper<MyClass1>>() { });
typeReferences.put(MyClass2.class, new ParameterizedTypeReference<ResponseWrapper<MyClass2>>() { });
...
ParameterizedTypeReference typeRef = typeReferences.get(clazz);
ResponseEntity<ResponseWrapper<T>> response = restTemplate.exchange(
uri,
HttpMethod.GET,
null,
typeRef);
and this finally worked.
If anyone has an example with parametrisation, I'd be very grateful to see it.
My own implementation of generic restTemplate call:
private <REQ, RES> RES queryRemoteService(String url, HttpMethod method, REQ req, Class reqClass) {
RES result = null;
try {
long startMillis = System.currentTimeMillis();
// Set the Content-Type header
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(new MediaType("application","json"));
// Set the request entity
HttpEntity<REQ> requestEntity = new HttpEntity<>(req, requestHeaders);
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
// Add the Jackson and String message converters
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
// Make the HTTP POST request, marshaling the request to JSON, and the response to a String
ResponseEntity<RES> responseEntity = restTemplate.exchange(url, method, requestEntity, reqClass);
result = responseEntity.getBody();
long stopMillis = System.currentTimeMillis() - startMillis;
Log.d(TAG, method + ":" + url + " took " + stopMillis + " ms");
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
return result;
}
To add some context, I'm consuming RESTful service with this, hence all requests and responses are wrapped into small POJO like this:
public class ValidateRequest {
User user;
User checkedUser;
Vehicle vehicle;
}
and
public class UserResponse {
User user;
RequestResult requestResult;
}
Method which calls this is the following:
public User checkUser(User user, String checkedUserName) {
String url = urlBuilder()
.add(USER)
.add(USER_CHECK)
.build();
ValidateRequest request = new ValidateRequest();
request.setUser(user);
request.setCheckedUser(new User(checkedUserName));
UserResponse response = queryRemoteService(url, HttpMethod.POST, request, UserResponse.class);
return response.getUser();
}
And yes, there's a List dto-s as well.
I feel like there's a much easier way to do this... Just define a class with the type parameters that you want. e.g.:
final class MyClassWrappedByResponse extends ResponseWrapper<MyClass> {
private static final long serialVersionUID = 1L;
}
Now change your code above to this and it should work:
public ResponseWrapper<MyClass> makeRequest(URI uri) {
ResponseEntity<MyClassWrappedByResponse> response = template.exchange(
uri,
HttpMethod.POST,
null,
MyClassWrappedByResponse.class
return response;
}
Abc is come object.
HttpEntity<Abc> httpEntity= new HttpEntity<>( headers );
ResponseEntity<Abc> resp = null;
resp = restCall( doUrl, HttpMethod.GET, httpEntity, new ParameterizedTypeReference<Abc>() {} );
//----------------------------------------------
public <T> ResponseEntity restCall( String doUrl, HttpMethod httpMethod, HttpEntity<?> httpEntity, ParameterizedTypeReference respRef )
{
try {
return restTemplate.exchange( doUrl, httpMethod, httpEntity, respRef );
}
catch( HttpClientErroException exc )
{
do whatever
}
}
//-------------------------- can also use a generic inside
public class ComResp<T> {
private T data;
public ComResp( T data )
{ this.data = data }
}
ResponseEntity<ComResp<Abc>> resp = null;
resp = restCall( doUrl, HttpMethod.GET, httpEntity, new ParameterizedTypeReference<ComResp<Abc>>() {} );
// spring boot 2.5.3
instead of Class<T> you can create a function that takes ParameterizedTypeReference<ResponseWrapper<T>> as paramemter :
public <T> ResponseWrapper<T> makeRequest(URI uri, ParameterizedTypeReference<ResponseWrapper<T>> response) {
ResponseEntity<ResponseWrapper<T>> response = template.exchange(
uri,
HttpMethod.POST,
null,
responseExchange);
return response;
}
when calling this function provide an instance of ParameterizedTypeReference with the response class in Generic:
makeRequest(url, new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {})
Related
I was trying to map response from API to a specific Class table, where Class is passed as an argument. I don't have problems with the same operation in ArrayList.
For example fetchTestPhotos(url, PhotoDto.class) should map response to ResponseEntity<PhotoDto[]>
I tried code below but it didn't work.
public <T> void fetchTestPhotos(String url, Class<T> type) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<T[]> response = restTemplate.getForEntity(url, T[].class);
if (response.getStatusCode().value() != 200) {
throw new CannotFetchData("Cannot fetch data from " + url);
}
}
You actually don't use the parameter type passed to the method which already holds Class<T> which can be PhotoDto.class as you say. Just use this parameter:
ResponseEntity<T[]> response = restTemplate.getForEntity(url, type);
Moreover, your method returns void type and the response is unused. Change the signature and return the response.
public <T> ResponseEntity<T> fetchTestPhotos(String url, Class<T> type) {
....
return response;
}
Finally, if you want to return ResponseEntity<T[]> with generic array, you have also to change the formal parameters. The T and T[] are not interchangeable.
public <T> ResponseEntity<T[]> fetchTestPhotos(String url, Class<T[]> type) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<T[]> response = restTemplate.getForEntity(url, type);
if (response.getStatusCode().value() != 200) {
throw new CannotFetchData("Cannot fetch data from " + url);
}
return response;
}
I have a helper class calling a REST web service. This helper class receives the information from the web service within a class defined as a generic "R". I can easily use GSON to read my JSON in a POJO, but I can't figure out how to give it a List and read it properly.
So far I have tried to extract the type of R in a variable and pass it to the List without success. I have tried using Jackson with its TypeFactory, also without success, it won't take R as its parameter.
What I want to do is something like this :
resource = new GsonBuilder().create().fromJson(response,List<R>)
or with Jackson
resource = mapper.readValue(response, List<R>);
"response" being my JSON string and resource the instance of List I want to return.
I can use either GSON or Jackson. I have been trying with both since some things looked more possible with one or the other.
I was able to make it work with a non generic, but all I get with a generic is a list of list of Map of the JSON fields. It's basically ignoring the type of R.
Also, R is extended from a class called BaseResource, so I know R will always be a child of BaseResource, but the fields I need are in the child.
Bonus: my true end goal is to have my generic R be a list of a specific object. So if I could instead have :
resource = new GsonBuilder().create().fromJson(response,R)
and R can be either a BaseResource or a List. I already made it work for BaseResource, but I need it to make it work for List somehow either by my duplicating most of it and explicitely states it's a List or passing List to R.
Our current workaround is to have a wrapper that contains a list:
private class Result{
private List<BaseResource> result;
}
but we want the web service to return lists instead of wrappers like that.
With GSON you have to implement your own serializer see: Type Information within serialized JSON using GSON
If you were to use Jackson you can solve this using #JsonTypeInfo annotation
polymorphic types: https://www.logicbig.com/tutorials/misc/jackson/jackson-json-type-info-annotation.html
did not read if you use any framework, well if you use spring you can use restTemplate and Jackson, here is an example of a generic class to call a rest service
private <Q, R> R callGeneric(String url, Q query, Class<R> responseClass) {
R response = null;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Q> req = new HttpEntity<Q>(query, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
ResponseEntity<R> callResult = null;
try{
callResult = restTemplate.exchange(url, HttpMethod.POST, req, responseClass);
}catch(HttpClientErrorException e){
logger.error(e.getStatusCode());
logger.error(e.getResponseBodyAsString());
throw e;
}
if (callResult == null) {
logger.error("callGeneric: No response (void) of the rest service");
return null;
}
response = callResult.getBody();
if (response == null) {
logger.error("callGeneric: empty response) ");
return null;
}
return response;
}
public ObjectResponse setCustCommunicationSetup(objectRequest request) {
String url = restCommunicationHostServer + custCommunicationContext + custCommunicationServiceSetCommunicationSetup;
logger.debug("callGeneric: "+url);
return callGeneric(url, request, ObjectResponse.class);
}
a similar problem is described hear maybe it will still help you
You must create correctly Type if you want deserialize List of generic
public static <T> List<T> deserialize(String json, Class<T> clazz) {
Type type = TypeToken.getParameterized(List.class,clazz).getType();
return new Gson().fromJson(json, type);
}
OR
Type myType = new TypeToken<List<DTO1>>() {}.getType();
List<DTO1> deserialize = new Gson().fromJson(json, myType);
OR
Yours workaround -- for me is good
problem is T because Java does not know what i kind and generate Type of T
public static <T> List<T> sec(String json, Class<T> clazz) {
Type type1 = new TypeToken<List<T>>() {}.getType();
Type type2 = new TypeToken<List<DTO1>>() {}.getType();
Type type = TypeToken.getParameterized(List.class, clazz).getType();
System.out.println(type1); //==>....*List<T>
System.out.println(type2); //==>....*List<....DTO1> --> this declaration In Java
System.out.println(type); //==>....*List<....DTO1>
return new Gson().fromJson(json, type);
}
Testing
this is test for more example to correct run
package pl.jac.litsofgeneriric;
import java.lang.reflect.Type;
import java.util.List;
import java.util.UUID;
import org.junit.Assert;
import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public class DeserializeListGenericTest {
#Test
public void deserializeDTO1() {
//given
String json = "[{\"id\":1,\"name\":\"test1\"},{\"id\":2,\"name\":\"test2\"}]";
//when
List<DTO1> deserialize = DeserializeListGeneric.deserialize(json, DTO1.class);
//then
Assert.assertEquals("test1", deserialize.get(0).name);
}
#Test
public void deserializeDTO2() {
//given
String json = "[{\"uuid\":\"97e98a6d-aae8-42cb-8731-013c207ea384\",\"name\":\"testUUID1\"},{\"uuid\":\"36c60080-7bb2-4c71-b4d8-c5a0a5fa47a2\",\"name\":\"testUUID1\"}]";
//when
List<DTO2> deserialize = DeserializeListGeneric.deserialize(json, DTO2.class);
//then
Assert.assertEquals("testUUID1", deserialize.get(0).name);
}
#Test
public void deserializeMyType() {
//given
String json = "[{\"id\":1,\"name\":\"test1\"},{\"id\":2,\"name\":\"test2\"}]";
//when
Type myType = new TypeToken<List<DTO1>>() {
}.getType();
List<DTO1> deserialize = new Gson().fromJson(json, myType);
//then
Assert.assertEquals("test1", deserialize.get(0).name);
}
#Test
public void deserializeGsonBuilder() {
//given
String json = "[{\"id\":1,\"name\":\"test1\"},{\"id\":2,\"name\":\"test2\"}]";
//when
Type myType = new TypeToken<List<DTO1>>() {
}.getType();
List<DTO1> deserialize = new GsonBuilder().create().fromJson(json, myType);
//then
Assert.assertEquals("test1", deserialize.get(0).name);
}
#Test
public void useSystemOut() {
//given
String json = "[{\"id\":1,\"name\":\"test1\"},{\"id\":2,\"name\":\"test2\"}]";
//when
List<DTO1> deserialize = sec(json, DTO1.class);
//then
Assert.assertEquals("test1", deserialize.get(0).name);
}
public static <T> List<T> sec(String json, Class<T> clazz) {
Type type1 = new TypeToken<List<T>>() {}.getType();
Type type2 = new TypeToken<List<DTO1>>() {}.getType();
Type type = TypeToken.getParameterized(List.class, clazz).getType();
System.out.println(type1); //==>....*List<T>
System.out.println(type2); //==>....*List<....DTO1> --> this declaration In Java
System.out.println(type); //==>....*List<....DTO1>
return new Gson().fromJson(json, type);
}
public static class DTO1 {
public Long id;
public String name;
}
public static class DTO2 {
public UUID uuid;
public String name;
}
}
and class DeserializeListGeneric
package pl.jac.litsofgeneriric;
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class DeserializeListGeneric {
public static <T> List<T> deserialize(String json, Class<T> clazz) {
Type type = TypeToken.getParameterized(List.class,clazz).getType();
return new Gson().fromJson(json, type);
}
}
This is the method which call the generic class method.
ServiceHandler<MyClass> serviceHandler = new ServiceHandler<MyClass>();
List<MyClass> res = serviceHandler.genericGetAll("https://api.github.com/users/hadley/orgs", MethodTypes.GET.toString(), null);
Here is the generic class with generic method
public class ServiceHandler<T>{
public ServiceHandler() {
}
public List<T> genericGetAll(String destinationUrl, String method, HashMap<String, String> params) {
List<T> genericResponse = null;
String httpResponse = httpClient(destinationUrl, method, params);
genericResponse = createListResponseHandler(httpResponse);
return genericResponse;
}
private List<T> createListResponseHandler(String string_response) {
return gson.fromJson(string_response, new TypeToken<List<T>>() {
}.getType());
}
Now the problem is I never get the MyClass reference in T. Where I am doing wrong? . I have done lots of google but still not found the solution.
My question is how to get MyClass reference in T. Because I need to serialized the data using gson.
I have tried. But still no solution
List<MyClass> res = serviceHandler.<MyClass>genericGetAll("https://api.github.com/users/hadley/orgs", MethodTypes.GET.toString(), null);
Generic type tokens don't work.
You need to pass in the new TypeToken<List<MyClass>>() {} as a constructor parameter to ServiceHandler:
public class ServiceHandler<T> {
private final TypeToken<List<T>> typeToken;
public ServiceHandler(TypeToken<List<T>> typeToken) {
this.typeToken = typeToken;
}
// ...
private List<T> createListResponseHandler(String string_response) {
return gson.fromJson(string_response, typeToken.getType());
}
}
and instantiate using:
ServiceHandler<MyClass> serviceHandler =
new ServiceHandler<>(new TypeToken<MyClass>() {});
I'm trying to do something like this :
public class ResponseProcessorFactory {
public static <T> ResponseProcessor<T> newResponseProcessor(){
return new GsonResponseProcessor<T>();
}
}
public class GsonResponseProcessor<T> implements ResponseProcessor<T> {
protected T response;
protected TypeToken typeToken;
public GsonResponseProcessor() {
this.typeToken = new TypeToken<T>(){};
}
#Override
public void parse(String jsonString) throws JSONException, IOException {
response = GsonHelper.getGsonInstance().fromJson(jsonString, typeToken.getType());
}
public T getResponse() {
return response;
}
}
private void ResponseProcessor getResponseProcessor(){
return ResponseProcessorFactory<List<String>>.newResponseProcessor();
}
Now, whenever I invoke getResponseProcessor(), it doesn't return me the response processor for List<String>. Rather, it returns the default response processor for Object.
I'm sure, I'm missing some concept regarding generic. Can someone explain in detail ?
EDIT :
The real usage is like this :
public BaseRequestWithResponseProcessor<List<Dashboard>> getDashboards(Listener<List<Dashboard>> responseListener, ErrorListener errorListener) {
String url = mBaseUrl + "/dashboard";
ResponseProcessor<List<Dashboard>> responseProcessor = ResponseProcessorFactory.newResponseProcessor();
AuthInfo authInfo = getAuthInfo();
BaseRequestWithResponseProcessor<List<Dashboard>> request = new BaseRequestWithResponseProcessor<List<Dashboard>>(
Method.GET, url, authInfo, null, responseProcessor, responseListener, errorListener);
return request;
}
In the GsonResponseProcessor constructor type erasure has happened and at runtime only one version of the method will exist with the type variable T converted to Object.
In Java only one version of generic methods and classes will exist, the type parameters only exist during compile-time and will be replaced by Object during run-time.
Type tokens must be constructed with a concrete type to capture the type information. This is the whole point with them, to capture type information at a place where the concrete type is known. The token can then be stored in variables and later be used to lookup objects or get hold of the type information with reflection.
The solution here is that the caller of getResponseProcessor who knows the concrete type creates the type token and passes it as a parameter. You could also pass in a Class object if that works in you situation. If you want to use generic classes as tokens however, as in your example with List<Dashboard> you will need a type token.
Something like this:
ResponseProcessor<List<String>> p = ResponseProcessorFactory.newResponseProcessor(new TypeToken<List<String>>() {});
You can work around the type erasure by passing in the class type as method parameter.
public class ResponseProcessorFactory {
public static <T> ResponseProcessor<T> newResponseProcessor(Class<T> type){
return new GsonResponseProcessor<T>(type);
}
}
public class GsonResponseProcessor<T> implements ResponseProcessor<T> {
protected T response;
protected TypeToken typeToken;
public GsonResponseProcessor(Class<T> type) {
this.typeToken = TypeToken.get(type);//depends on the API version
//this.typeToken = new TypeToken<T>(type);
//this.typeToken = TypeToken.of(type);
}
#Override
public void parse(String jsonString) throws JSONException, IOException {
response = GsonHelper.getGsonInstance().fromJson(jsonString, typeToken.getType());
}
public T getResponse() {
return response;
}
}
Have you tried changing the signature to the correct type, too?
private ResponseProcessor<List<String>> getResponseProcessor() {
return ResponseProcessorFactory.newResponseProcessor();
}
I am trying to make the three following methods into one generic solution, I tried some ideas which compile but don't do well at runtime.
public static List<User> parseToUsers(HttpResponse response) {
ObjectMapper mapper = new ObjectMapper();
String results = parseToString(response);
return mapper.readValue(results, new TypeReference<List<User>>() {});
}
public static List<Record> parseToRecords(HttpResponse response) {
ObjectMapper mapper = new ObjectMapper();
String results = parseToString(response);
return mapper.readValue(results, new TypeReference<List<Record>>() {});
}
public static Record parseToRecord(HttpResponse response) {
ObjectMapper mapper = new ObjectMapper();
String results = parseToString(response);
return mapper.readValue(results, new TypeReference<Record>() {});;
}
I have also tried to understand this blog post about Super Type Tokens.
EDIT:
This is what I came up with so far:
public static <T> T parseJsonResponse(TypeReference<T> type, HttpResponse response) throws DroidException {
ObjectMapper mapper = new ObjectMapper();
String results = parseResponseToString(response);
return readValue = mapper.readValue(results, type);
}
Then I call it like this.
parseJsonResponseToList(new TypeReference<List<Record>>() {}, response)
Not really satisfieng.Is there a better solution?
So what exactly is the problem? In what way do you not like it?
Jackson has other ways for constructing generic types; so perhaps what are looking for is along lines of:
public List<T> listOf(String json, Class<T> elementType) {
ObjectMapper mapper = new ObjectMapper(); // should (re)use static instance for perf!
JavaType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, elementType);
return mapper.readValue(json, listType);
}
TypeFactory can be used to programmatically construct types that use generics -- return type is JavaType, because basic Java Class is type-erased.
TypeFactory is actually used to convert TypeReference to JavaType internally as well.
EDIT
As to regular, non-Collection/Map types, it's really quite simple:
public T parseSingle(Class<T> cls, InputStream src) throws IOException {
return mapper.readValue(src, cls);
}
(you also do NOT want to read contents as String -- not only is it slow, but it's easy to mess up character encodings, so if possible, feed InputStream or byte[] instead)
I don't really know what your ObjectMapper and TypeReference classes do, so maybe this answer doesn't fit you all that well, but here's how I'd probably do it if I understand your situation at all:
public interface Parser<T> {
public T parse(String results);
public static class MapperParser<T> implements Parser<T> {
private final TypeReference<T> type;
public MapperParser(TypeReference<T> type) {this.type = type;}
public T parse(String results) {
return(new ObjectMapper().readValue(results, type));
}
}
public static final Parser<List<User>> users = new MapperParser(new TypeReference<List<User>>());
public static final Parser<List<Record>> records = new MapperParser(new TypeReference<List<Record>>());
public static final Parser<Record> record = new MapperParser(new TypeReference<Record>());
}
/* And then, in the class you were in your question: */
public static <T> T parseJsonResponse(Parser<T> parser, HttpResponse response) {
return(parser.parse(parseResponseToString(response)));
}
Then, you may call it as such:
parseJsonResponse(Parser.users, response)
Is that more to your liking?
Ok this is my favorite solution, inspired by Dolda2000, i keep as is in my initial post and add an enum.
public enum TypeRef {
RECORDS(new TypeReference<List<Record>>() {}), USERS(new TypeReference<List<User>>() {}), USER(new TypeReference<User>() {});
private TypeReference<?> type;
private TypeRef(TypeReference<?> type) {
this.type = type;
}
public TypeReference<?> getType() {
return this.type;
}
}
and then instead of writing:
readJsonResponse(new TypeReference<List<Record>>() {}, response)
i can write:
readJsonResponse(TypeRef.RECORDS, response);
no magic going on but i like it more than wrapping it in another interface
Ok after running into a compile error:
type parameters of T cannot be determined; no unique maximal instance exists for type variable T with upper bounds T,java.lang.Object
i quit the over engeneering session and keep it simple
private static TypeReference<List<Record>> RECORDS = new TypeReference<List<Record>>() {};
public static <T> T readJson(TypeReference<T> type, String text) {
ObjectMapper mapper = new ObjectMapper();
return readValue = mapper.readValue(text, type);
}
use it like this
readJson(RECORDS, text);
no enums, i just use static fields for the TypeReference and everyone can read the code easily without understanding TypeReference
thank you guys i learned something about over engeneering today :P