Deserializing Generic Types with Gson

Deserializing Generic Types with GSON

You have to specify the type of T at the time of deserialization. How would your List of posts get created if Gson didn't know what Type to instantiate? It can't stay T forever. So, you would provide the type T as a Class parameter.

Now assuming, the type of posts was String you would deserialize MyJson<String> as (I've also added a String json parameter for simplicity; you would read from your reader as before):

doInBackground(String.class, "{posts: [\"article 1\", \"article 2\"]}");

protected MyJson<T> doInBackground(Class<T> type, String json, Void... params) {

GsonBuilder gson = new GsonBuilder();
Type collectionType = new TypeToken<MyJson<T>>(){}.getType();

MyJson<T> myJson = gson.create().fromJson(json, collectionType);

System.out.println(myJson.getPosts()); // ["article 1", "article 2"]
return myJson;
}

Similarly, to deserialize a MyJson of Boolean objects

doInBackground(Boolean.class, "{posts: [true, false]}");

protected MyJson<T> doInBackground(Class<T> type, String json, Void... params) {

GsonBuilder gson = new GsonBuilder();
Type collectionType = new TypeToken<MyJson<T>>(){}.getType();

MyJson<T> myJson = gson.create().fromJson(json, collectionType);

System.out.println(myJson.getPosts()); // [true, false]
return myJson;
}

I've assumed MyJson<T> for my examples to be as

public class MyJson<T> {

public List<T> posts;

public List<T> getPosts() {
return posts;
}
}

So, if you were looking for to deserialize a List<MyObject> you would invoke the method as

// assuming no Void parameters were required
MyJson<MyObject> myJson = doInBackground(MyObject.class);

Deserializing Generic Type with GSON

Just use Gson().fromJson(json, T.class); no TypeToken needed

GSON deserialization with generic types and generic field names

One idea would be to define a custom generic deserializer. Its generic type will represent the concrete class of the list's elements wrapped in a Body instance.

Assuming the following classes:

class Body<T> {
private List<T> list;

public Body(List<T> list) {
this.list = list;
}
}

class Cat {
private int id;
private String title;

...
}

class Truck {
private int id;
private String engine;
private int wheels;

...
}

The deserializer assumes that the structure of the json is always the same, in the sense that you have an object that contains an object named "body". Also it assumes that the value in the first key-value pair of this body is a list.

Now for each element in the json array, we need again to fetch the inner object associated with each key. We deserialize this value and put it in the list.

class CustomJsonDeserializer<T> implements JsonDeserializer<Body<T>> {

private final Class<T> clazz;

public CustomJsonDeserializer(Class<T> clazz) {
this.clazz = clazz;
}

@Override
public Body<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject body = json.getAsJsonObject().getAsJsonObject("body");
JsonArray arr = body.entrySet().iterator().next().getValue().getAsJsonArray();
List<T> list = new ArrayList<>();
for(JsonElement element : arr) {
JsonElement innerElement = element.getAsJsonObject().entrySet().iterator().next().getValue();
list.add(context.deserialize(innerElement, clazz));
}
return new Body<>(list);
}
}

For the final step, you just need to create the corresponding type, instantiate and register the adapter in the parser. For instance for trucks:

Type truckType = new TypeToken<Body<Truck>>(){}.getType();
Gson gson = new GsonBuilder()
.registerTypeAdapter(truckType, new CustomJsonDeserializer<>(Truck.class))
.create();
Body<Truck> body = gson.fromJson(new FileReader("file.json"), truckType);

You can even return the list directly from the adapter if you want to get rid of the Body class.

With the trucks you'll get [1_big_12, 2_super big_128] as output and [1_cat1, 2_cat2] with the cats.

Using a generic type with Gson

The simplest approach for what you are attempting is to define the type of class which is to be returned in the method signature itself. You can do so by either passing an instance of 'T' to the method or the Class of the value to be returned. The second approach is the more typical in cases where you expect to generate a return value. Here is an example of this approach using Gson:

public <T> T deserialize(String jsonString, Class<T> clazz) {
GsonBuilder builder = new GsonBuilder();
builder.setDateFormat("MM/dd/yy HH:mm:ss");

Gson gson = builder.create();
return gson.fromJson(jsonString, clazz);
}

Usage:

MyClass mc = deserialize(jsonString, MyClass.class);

Trouble deserializing generic types using GSON

The whole point of anonymous TypeToken subtypes is that they're defined at compile time so that generic type information is saved. You'll need to do something to make the token information available dynamically. This approach might work:

private final TypeToken<ArrayList<T>> targetType;

public Storage(Class<T> dataType) {
targetType = new TypeToken<ArrayList<T>>(){}
.where(new TypeParameter<T>(){}, dataType);
}

public List<T> readAll() { // avoid using concrete ArrayList in public API;
// not a bad idea to change it in the other locations
...
return GSON.fromJson(objectsJson, targetType);
}

Gson fromJson deserialize generics

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!

How do I deserialize generic object T with Gson?

As you've already discussed in the comments, it's not possible for Gson to be really brilliant and figure out what the original object was. If you spend 5 minutes thinking about how that would have to work, you'll realize it's impossible.

However, you might be able to, in a custom deserializer, analyze the Json and figure out what type it was supposed to be. Gson already has RuntimeTypeAdapterFactory for this. See this discussion for a working example: https://stackoverflow.com/a/22081826/1768232

Basically, you can use this factory to specify the type in the json itself, (it will look like "type":"typename", which will make it possible for you to do the deserialization.

If modifying the json is not an option, you can still write a custom deserializer to analyze the json and attempt to figure out what type it's supposed to be, but that logic is much more difficult.



Related Topics



Leave a reply



Submit