I have REST web service that has different responses containing some user information like this
{"id": 1,"fullname": "NV","login": "bravenewuser","sex": "M","mail":"aaa#gmail.com","photo": "","status": "","birthDate": "1900-01-11T00:00:00Z","registrationDate": "1900-05-13T00:00:00Z","phoneNumber": "9010"}
and like this
[{"id":2,"userId":0,"timeStart":"2017-08-21T13:05:00Z","active":true,"title":"Woman","artist":"Wolfmother","length":"00:04:00"},{"id":1,"userId":0,"timeStart":"2017-08-20T13:05:34Z","active":true,"title":"Dont call","artist":"Royal blood","length":"00:03:20"}]
with list of user's listenings
Say I have two classes User and Listening (may be in addition I need ListOfListenings wrapper-class). May be in future i would like to retrieve some more information from service and get responses having some another structure.
The question is what is the best way to parse different responses in one place (method or class) of the code?
I use com.loopj.android.http.AsyncHttpClient for calling service. So can I try something like this:
Write one common response handler for different types of response:
public class CommonResponseHandler <T extends Entity> extends AsyncHttpResponseHandler {
private T response;
public T getResponse() {
return this.response; //return pasing result
}
#Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
/*parsing response body into this.response*/
}
#Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
///...
}
}
and then call service like this
public User getUser(String url, RequestParams someparams) {
AsyncHttpClient client = new AsyncHttpClient();
CommonResponseHandler<User> handler = new CommonResponseHandler<>();
client.get(url, someparams, handler );
return handler.getResponse();
}
Can I simplify parsing different types of responses by this way. And if not, what is better (or even the best) way to solve this problem? Thank you.
You need two response handlers.
public class ObjectResponseHandler <T extends Entity> extends AsyncHttpResponseHandler {
private T response;
public T getResponse() {
return this.response; //return pasing result
}
}
And,
public class ArrayResponseHandler <T extends Entity> extends AsyncHttpResponseHandler {
private List<T> responses;
public List<T> getResponses() {
return this.responses; //return pasing result
}
}
And, if your JSON is only going to be a JsonArray or JsonObject. You can check for it using
anyJsonString.startsWith("[") => true => use ArrayResponseHandler
anyJsonString.startsWith("{") => true => use ObjectResponseHandler
If you want to keep using getResponse() as an object, you should create something like
public class ListWrapper<T> {
List<T> list;
public ListWrapper (List<T> list) { this.list = list; }
public List<T> getList () { return list; }
}
And, use it in ArrayResponseHandler, like
public ListWrapper getResponse() {
return new ListWrapper<T>(responses);
}
Related
My question is about the possibility to use RxJava for Android to manipulate data from a Retrofit call.
I've just started to use these libraries, so if my questions are trivial, please be patient.
This is my scenario.
I have a json returned from the server that looks like this
{ <--- ObjectResponse
higher_level: {
data: [
{
...
some fields,
some inner object: {
....
},
other fields
}, <----- obj 1
....,
{
....
}<---- obj n
]<-- array of SingleObjects
}
} <--- whole ObjectResponse
I've already have retrofit get this response and parsed in a ObjectResponse. Parsing this object, I can obtain a List that I can pass as usual to my RecyclerView Adapter.
So retrofit returned the ObjectResponse which is the model for the entire server answer, and in the retrofit callback I manipulate ObjectResponse to extract my List to be then passed to my adapter.
Right now, I have something like this
Call<ObjectResponse> call = apiInterface.getMyWholeObject();
call.enqueue(new Callback<ObjectResponse>() {
#Override
public void onResponse(Call<ObjectResponse> call, Response<ObjectResponse> response) {
//various manipulation based on response.body() that in the ends
// lead to a List<SingleObject>
mView.sendToAdapter(listSingleObject)
}
#Override
public void onFailure(Call<ObjectResponse> call,
Throwable t) {
t.printStackTrace();
}
});
My question is:
Is there a way to obtain from retrofit an Observable that can ultimate lead me to emit the list of SingleObject (and manipulate it) without have to manipulate ObjectResponse as I would do in the retrofit callback? Or should I have to stick with the retrofit callback and only after obatin List I can manipulate with RxJava just before feed this list to my Adapter?
I'd like to obtain something like this
apiInterface
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<SingleObject>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Post> posts) {
mView.sendToAdapter(listSingleObject)
}
});
Retrofit converter factories solve that issue very nicely.
Jake Wharton talks about "envelope" objects in his "Making Retrofit Work For You" talk and points out how that can be solved.
Having defined an envelope class - a class with some extra fields that you do not care about:
public class Envelope<T> {
Meta meta;
List<Notification> notifications;
T response;
}
In this POJO fields meta and List<Notification> are being returned from the backend, but in the context of android app they are not interesting to us. Assume, that the real value that you need from the response is field named response, which might be any object (because it's generic).
Particularly in your example the POJO structure would be like this:
public class OriginalResponse {
HigherLevel higher_level;
}
public class HigherLevel {
Data data;
}
public class Data {
List<ActualData> list;
}
You have to implement your custom Converter.Factory:
public class EnvelopingConverter extends Converter.Factory {
#Nullable
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
Type envelopedType = TypeToken.getParameterized(Envelope.class, type).getType();
Converter<ResponseBody, Envelope<?>> delegate = retrofit.nextResponseBodyConverter(this, envelopedType, annotations);
return body -> {
Envelope<?> envelope = delegate.convert(body);
// Here return the object that you actually are interested
// in your example it would be:
// originalResponse = delegate.convert(body);
// return originalResponse.higher_level.data.list;
return envelope.response;
};
}
}
Add this converter to your retrofit builder:
new Retrofit.Builder()
...
.addConverterFactory(new EnvelopingConverter())
...
Then in your retrofit api interface instead of returning Single<OriginalResponse> return Single<List<ActualData>> directly:
interface Service {
#GET(...)
Single<List<ActualData>> getFoo();
}
Typical implementation in Kotlin:
class EnvelopeConverterFactory : Converter.Factory() {
override fun responseBodyConverter(type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *>? {
val envelopedType: Type = TypeToken.getParameterized(ParentObject::class.java, type).type
val delegate: Converter<ResponseBody, ParentObject> =
retrofit.nextResponseBodyConverter(this, envelopedType, annotations)
return Converter<ResponseBody, ChildObject> { body -> delegate.convert(body)?.childObject }
}
}
As far as I know, there's no way to do it. For best practice, you should create a Facade layer (maybe an ApiManager class) to manage all your APIs. In that case, you can use map/flatMap to map your ObjectResponse to SingleObject like:
public Observable<List<SingleObject>> getSingleObjects(){
return ServiceGenerator.getApiMethods().getObjectResponse.map(new Function<ObjectResponse, List<SingleObject>>() {
#Override
public List<SingleObject> apply(ObjectResponse response) throws Exception {
return reponse.getListSingleObjects();
}
})
}
After some days, I can post my own solution.
It is inspired by the idea suggested by azizbekian.
The center idea is on the Envelope class, which I've express using retrofit annotation to be sure it would adapt to different JSON response from server, parsing the
higher_level: {
data: [
mid_level: { .. },
...
]
}
structure that I've already explained in my original post
public class WrapperResponse<T> {
#SerializedName(value="higher_level", alternate={"mid_level", "other"})
#Expose
DataResponse<T> data;
public DataResponse<T> getData() {
return data;
}
public void setData(DataResponse<T> data) {
this.data = data;
}
}
The focus here is in the parameters of SerializedName, where I specify all the possible JSON objects name that appear in my server response.
Then I have
public class UnwrapConverterFactory extends Converter.Factory {
private GsonConverterFactory factory;
public UnwrapConverterFactory(GsonConverterFactory factory) {
this.factory = factory;
}
#Override
public Converter<ResponseBody, ?> responseBodyConverter(final Type type,
Annotation[] annotations, Retrofit retrofit) {
Type wrappedType = new ParameterizedType() {
#Override
public Type[] getActualTypeArguments() {
return new Type[] {type};
}
#Override
public Type getOwnerType() {
return null;
}
#Override
public Type getRawType() {
return WrapperResponse.class;
}
};
Converter<ResponseBody, ?> gsonConverter = factory
.responseBodyConverter(wrappedType, annotations, retrofit);
return new WrapperResponseBodyConverter(gsonConverter);
}
}
and
public class WrapperResponseBodyConverter<T>
implements Converter<ResponseBody, T> {
private Converter<ResponseBody, WrapperResponse<T>> converter;
public WrapperResponseBodyConverter(Converter<ResponseBody,
WrapperResponse<T>> converter) {
this.converter = converter;
}
#Override
public T convert(ResponseBody value) throws IOException {
WrapperResponse<T> response = converter.convert(value);
return response.getData().getData();
}
}
Used in my Retrofit Module (dagger2) to ensure that my Retrofit client unwrap any answer from server using the generic WrapperResponse and, in the end, I can write Retrofit method as
#GET("locations")
Observable<List<Location>> getLocation();
where List is exactly the result I wanted to obtain: a list of objects straight from Retrofit response, that I can further elaborate with RxJava.
Thanks all.
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>>() {})
I was trying to get my head around generics of java. And it seems difficult to implement what I want.
I want to make a generic task aggregator. Meaning, given a list of request, I need to process each request through some handler (that is obtained from a factory based on request). And return the list of response. Now to speed up the process, I want to obtain the results by executing handlers in parallel.
First I've define a interface THandler:
interface THandler<Request, Response> {
Response handle(Request request);
}
This is my aggregator:
public class AggTasks
{
public static <Request, Response> long aggregate(List <Request> req, List <Response> res) {
CountDownLatch allDone = new CountDownLatch(req.size());
List <TWork<Request, Response>> workers = new ArrayList<>();
for (Request r : req) {
// Getting error: The constructor TWork<Request,Response>(THandler<Request,Object>, Request, CountDownLatch) is undefined
workers.add(new TWork<Request, Response>(getHandler(r), r, allDone));
}
for (TWork<Request, Response> w : workers) {
new Thread(w).start();
}
allDone.await();
for (TWork<Request, Response> w : workers) {
res.add(w.collectResponse());
}
return 0;
}
}
This is my TWork:
class TWork<Request, Response> implements Runnable{
private THandler<Request, Response> handler;
private Request request;
private Response response;
public TWork(THandler<Request, Response> tHandler, Request r, CountDownLatch allDone)
{
this.handler = tHandler;
this.request = r;
}
public Response collectResponse() {
return response;
}
#Override
public void run()
{
response = handler.handle(request);
}
}
Finally here is getHandler() and FactoryHandler. I tried explicitly casting the return value to THandler<Request, Response>, however, still at compile time it's showing as THandler<Request,Object>:
#SuppressWarnings("unchecked")
private static <Request, Response> THandler<Request, Response> getHandler(Request req) {
return (THandler<Request, Response>) FactoryHandler.getHandler(req);
}
class FactoryHandler {
#SuppressWarnings("unchecked")
public static <Request, Response> THandler<Request, Response> getHandler(Request req) {
return (THandler<Request, Response>) new StringHandler();
}
}
Any suggestions to improve the design is welcome.
Edit:
I tried the suggestion of Wietlol: and it seems to work if I skip call to static method getHandler():
workers.add(new TWork<Request, Response>(FactoryHandler.<Request, Response>getHandler(r), r, allDone));
FactoryHandler.getHandler(req);
Only defines the type of "Request", implicitly defined by the type of "req".
Response is not defined and thus predicted as Object to make it work with everything.
The easiest solution would be to call "getHandler()" like this:
FactoryHandler.<Request, Response>getHandler(req);
Which explicitly defines the generic types.
I want to interact with a RESTful webservice that responds only in JSON.
Any successful response from the server has this syntax:
{
"code": int code,
"data": object or list of objects
}
while on error response:
{
"code": int code,
"error": string,
"details": string
}
So I made two classes in my Android project like this (for GSON reflection):
public class ErrorEntity {
private String details;
private String error;
private int code;
public ErrorEntity() {
// Stub constructor
}
public String getDetails() {
return details;
}
public String getError() {
return error;
}
public int getCode() {
return code;
}
}
For a successful response I made a generic because I don't want to parse JSON data on overridden parseNetworkResponse:
public class SuccessfulEntity<T> {
private T data;
private int code;
public SuccessfulEntity() {
// Stub content
}
public T getData() {
return data;
}
public int getCode() {
return code;
}
}
Now, because my RESTful server requires some custom headers, I need to make a Request subclass, but I don't know from which class I need to inherit.
I saw this question: Send POST request with JSON data using Volley and though to do something like that.
Basically, I want to make a new class (VolleyRestClient) which has GET, POST, DELETE methods and API routings, and with this class make all requests I need to do.
Methods of this class need to make a new custom request and parse new objects response like SuccessfulEntity and ErrorEntity, and then parsing data in service/thread that make the VolleyRestClient call.
How can I do that?
After a long fight with generics and type erasure, I finally did it.
So I'm posting this for whoever has the same issue like me and needs a solution without freaking out.
My ErrorEntity and my SuccessfulEntity are still the same, but I created a new interface called RepositoryListener, like this:
public interface RepositoryListener {
public abstract void onErrorResponse(int code, String details);
public abstract void onSuccessfulResponse(int code, Object obj);
public abstract void onSuccessfulResponse2(int code, List<Object> obj);
}
Then I made a class, VolleyRestClient, like this:
public class VolleyRestClient extends RestClient {
private final DefaultRetryPolicy mRetryPolicy;
private final RequestQueue mQueue;
private final Gson gson = new Gson();
public VolleyRestClient(Context context) {
// Default retry policy
mRetryPolicy = new DefaultRetryPolicy(2000, 3, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
mQueue = Volley.newRequestQueue(context);
}
public RequestQueue getQueue() {
// Method to push requests for image download
return mQueue;
}
#Override
public void GET(boolean obj, boolean needAuth, String url, Type type,
RepositoryListener listener) {
// Choose which listener to construct
Response.Listener<myResponse> mListener = obj ?
// This uses objects
makeSuccessfulListener(listener, type) :
// This uses list of objects
makeSuccessfulListener2(listener, type);
myRequest mRequest =
new myRequest(Request.Method.GET, needAuth, url,
mListener, makeErrorListener(listener));
mRequest.setRetryPolicy(mRetryPolicy);
mQueue.add(mRequest);
}
#Override
public void POST(boolean needAuth, String url, String body, Type type, RepositoryListener listener) {
myRequest mRequest = new myRequest(Request.Method.POST, needAuth, url, body,
makeSuccessfulListener(listener, type), makeErrorListener(listener));
mRequest.setRetryPolicy(mRetryPolicy);
mQueue.add(mRequest);
}
#Override
public void DELETE(boolean needAuth, String url, Type type, RepositoryListener listener) {
myRequest mRequest =
new myRequest(Request.Method.DELETE, needAuth, url,
makeSuccessfulListener(listener, type), makeErrorListener(listener));
mRequest.setRetryPolicy(mRetryPolicy);
mQueue.add(mRequest);
}
private Response.Listener<myRequest> makeSuccessfulListener
(final RepositoryListener listener, final Type type) {
// TODO: test this method and implement lists
if (listener == null) {
return null;
} else {
return new Response.Listener<myRequest>() {
#Override
public void onResponse(myRequest response) {
SuccessfulEntity<Object> obj = gson.fromJson(response.getBody(), type);
listener.onSuccessfulResponse(response.getCode(), obj.getData());
}
};
}
}
private Response.Listener<myRequest> makeSuccessfulListener2
(final RepositoryListener listener, final Type type) {
// TODO: test lists
if (listener == null) {
return null;
} else {
return new Response.Listener<myRequest>() {
#Override
public void onResponse(myReqyest response) {
SuccessfulEntity<List<Object>> obj = gson.fromJson(response.getBody(), type);
listener.onSuccessfulResponse2(response.getCode(), obj.getData());
}
};
}
}
private Response.ErrorListener makeErrorListener(final RepositoryListener listener) {
return new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
try {
String jError = new String(error.networkResponse.data);
ErrorEntity mError = gson.fromJson(jError, ErrorEntity.class);
// Invoke listener closure
listener.onErrorResponse(error.networkResponse.statusCode, mError.getDetails());
} catch (Exception e) {
listener.onErrorResponse(404, e.getMessage());
}
}
};
}
}
This is very dependant by my needs, but I'll explain the general concept.
So I have a custom request, as explained in my question, and I want to parse it to the correct data type.
To be more specific, I could have a JSONArray data only on GET requests (paginated elements, etc...) so I need to find a way to distinguish between this two cases (of course, I know in which cases I'll get a List or an Object).
We cannot simply create POJO from Json within a generic class using its type (because Java Type Erasure), so we need object type upfront.
But what we can do is:
in our custom request, on parseNetworkResponse, do something like that:
#Override
protected Response<myResponse> parseNetworkResponse(NetworkResponse response) {
try {
// Using server charset
myResponse mResponse = new myResponse();
mResponse.setCode(response.statusCode);
mResponse.setBody(new String(response.data,
HttpHeaderParser.parseCharset(response.headers)));
// Return new response
return Response.success(mResponse, HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// Normally use 'utf-8'
return Response.error(new ParseError(e));
}
}
In other words, copy the raw string response body onto a new object myResponse;
Response body will be eventually parsed in VolleyRestClient with the appropriate type passed as a GET/DELETE/POST argument;
makeSuccessfulListener and makeSuccessfulListener2 construct a Response.Listener from a RepositoryListener, which has 3 methods to override: onSuccessfulResponse for objects data, onSuccessfulResponse2 for list of objects data, onErrorResponse for 4XX/5XX errors;
Our data object/list will be parsed to more generics type (List and Object) and then passed to our custom listener RepositoryListener.
A full example for this approach:
public void getNewLogin(String nickname, String password,
final TextView author, final TextView title, final TextView text) {
String json =
(new StringBuilder()
.append("{ \"nickname\": \"")
.append(nickname)
.append("\", \"password\": \"")
.append(password)
.append("\" }")).toString();
mRest.POST(false, "http://192.168.0.104:8000/api/session", json,
new TypeToken<SuccessfulEntity<Login>>(){}.getType(),
new RepositoryListener() {
#Override
public void onSuccessfulResponse2(int code, List<Object> obj) {
// Nothing happens here
}
#Override
public void onSuccessfulResponse(int code, Object obj) {
UserSession mInstance = UserSession.getInstance(null);
Login newLogin = (Login) obj;
title.setText(newLogin.getToken());
mInstance.setToken(newLogin.getToken());
Log.i("onSuccessfulResponse", mInstance.getToken());
Log.i("onSuccessfulResponse", mInstance.getmAuthorizationToken());
if (newLogin.getUser() != null) {
author.setText(newLogin.getUser().getNickname());
text.setText(newLogin.getUser().getUniversity());
}
}
#Override
public void onErrorResponse(int code, String error) {
Log.i("onErrorResponse", error);
}
});
mRest is a VolleyRestClient object, which performs a POST request to that address with Type constructed by Gson TypeToken (remember, our body is a SuccessfulEntity).
Since we'll have an Object data for sure, we'll just override onSuccessfulResponse, cast data object to the same type T of SuccessfulEntity used in TypeToken, and do our dirty work.
I don't know if I was clear, this approach works, if some of you needs some clarification, just ask :)
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();
}