While working on an Android app I've faced a problem when I wanted to make an ultimate generic methods to send HTTP-requests (with loopj) and deserialize them (with Gson).
As you may know while using gson.fromJson
you cannot do like this:
gson.fromJson(responseBody, new TypeToken<T>() {}.getType());
or
gson.fromJson(responseBody, new TypeToken<ArrayList<T>>() {}.getType())
Instead of actual object (or list of actual objects in 2nd case) that you've passed as T
, you will get LinkedTreeMap
object (or list of LinkedTreeMap
objects).
If you want an still want to deserialize generic type object(-s) see the answer below.
+Bonus: ultimate generic solution for loopj+gson
Thanks for authors and commenters of these posts:
http://blog.xebia.com/2009/02/07/acessing-generic-types-at-runtime-in-java/
How to get concrete type of a generic interface
Java Type Generic as Argument for GSON
Deserializing JSON object to generic type java object
First we need to get the actual class of generic type T
.
We can do it by passing the class itself (Class<T> cl
) or by getting class from the object with generic type (SomeObject<T> someObjectWithGenericType
). I will use second case in examples.
Then we need to create a special object of class Element<T>
that will tell Gson what class to use for deserialization.
public <T> T getObject(String json, SomeObject<T> someObjectWithGenericType) {
Class cl = getTypeClassOfObject(someObjWithGenericType);
T object = gson.fromJson(json, new Element<T>(cl));
return object;
}
private Class getTypeClassOfObject(Object obj) {
return (Class) ((ParameterizedType) obj.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
private class Element<T> implements ParameterizedType {
private Class<T> cl;
public Element(Class<T> cl) {
this.cl = cl;
}
public Type[] getActualTypeArguments() {
return new Type[] {cl};
}
public Type getRawType() {
return cl;
}
public Type getOwnerType() {
return null;
}
}
If your SomeObject<T>
is an interface (perhaps a callback, as you will see in loopj examples later), you can use this method instead of getTypeClassOfObject
:
private Class getTypeClassOfInterfaceObject(Object obj) {
return (Class) ((ParameterizedType) obj.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
}
Deserializing JSON array to list of generic type java objects
Same idea, but we have a different special class to help Gson with deserialization:
public <T> List<T> getList(String json, SomeObject<T> someObjectWithGenericType) {
Class cl = getTypeClassOfObject(someObjWithGenericType);
List<T> list = gson.fromJson(json, new ListWithElements<T>(cl));
return list;
}
private class ListWithElements<T> implements ParameterizedType {
private Class<T> elementsClass;
public ListWithElements(Class<T> elementsClass) {
this.elementsClass = elementsClass;
}
public Type[] getActualTypeArguments() {
return new Type[] {elementsClass};
}
public Type getRawType() {
return List.class;
}
public Type getOwnerType() {
return null;
}
}
BONUS
As you will see here someObjectWithGenericType
is going to be a callback with generic type T
.
Even though I use loopj I'm sure any other async http client can be used to achieve same results.
loopj + Gson with generics: object
public <T> void getObject(String url, HashMap<String, String> paramsMap, final GetObjectCallback<T> callback) {
RequestParams params = convertParams(paramsMap);
client.get(url, params, new TextHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, String responseBody) {
try {
Class cl = getTypeClassOfInterfaceObject(callback);
T object = gson.fromJson(responseBody, new Element<T>(cl));
if (object != null) {
callback.onSuccess(object);
} else {
callback.onFailure();
}
} catch (Exception e) {
e.printStackTrace();
callback.onFailure();
}
}
@Override
public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) {
error.printStackTrace();
callback.onFailure();
}
});
}
private RequestParams convertParams(HashMap<String, String> paramsMap) {
RequestParams params = new RequestParams();
if (paramsMap != null) {
for (String key : paramsMap.keySet()) {
params.put(key, paramsMap.get(key));
}
}
return params;
}
public interface GetObjectCallback<T> {
void onSuccess(T item);
void onFailure();
}
loopj + Gson with generics: list
public <T> void getList(String url, HashMap<String, String> paramsMap, final GetListCallback<T> callback) {
RequestParams params = convertParams(paramsMap);
client.get(url, params, new TextHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, String responseBody) {
try {
Class cl = getTypeClassOfInterfaceObject(callback);
List<T> list = gson.fromJson(responseBody, new ListWithElements<T>(cl));
if (list != null) {
callback.onSuccess(list);
} else {
callback.onFailure();
}
} catch (Exception e) {
e.printStackTrace();
callback.onFailure();
}
}
@Override
public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) {
error.printStackTrace();
callback.onFailure();
}
});
}
public interface GetListCallback<T> {
void onSuccess(List<T> list);
void onFailure();
}
Usage: object
api.getObject(URL, paramsMap, new GetObjectCallback<NewsItem>() {
@Override
public void onSuccess(NewsItem item) {
// do something
}
@Override
public void onFailure() {
// do something
}
});
Usage: list
api.getList(URL, paramsMap, new GetListCallback<Comment>() {
@Override
public void onSuccess(List<Comment> list) {
// do something
}
@Override
public void onFailure() {
// do something
}
});
Any improvements are very welcome!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With