Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson fromJson deserialize generics

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

like image 314
Makks129 Avatar asked Oct 05 '14 14:10

Makks129


1 Answers

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!

like image 200
Makks129 Avatar answered Oct 04 '22 11:10

Makks129