69

I have some problems with implementation of Json Deserialization in my Android application (with Gson library)

I've made class like this

public class MyJson<T>{
    public List<T> posts;
}

And Deserialization call is:

public class JsonDownloader<T> extends AsyncTask<Void, Void, MyJson<T>> {
...
protected MyJson<T> doInBackground(Void... params) {
  ...
    Reader reader = new InputStreamReader(content);
    GsonBuilder gson = new GsonBuilder();
    Type collectionType = new TypeToken<MyJson<T>>() {}.getType();
    result = gson.create().fromJson(reader, collectionType);
  ...
  }
}

Problem is that result.posts list after call holds one Array of LinkedTreeMap Objects(with correct values so problem is Deserialization) instead of MyJson Objects. When I use MyObject instead of T everything is running fine and MyObject is correct.

So is there any way to implement deserialization call without creating custom deserializer?

VizGhar
  • 3,036
  • 1
  • 25
  • 38

6 Answers6

55

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);
Ravi K Thapliyal
  • 51,095
  • 9
  • 76
  • 89
  • 8
    and where exactly is `Class type` used in your code? – hoefling Dec 29 '15 at 16:09
  • @Mr.Yetti The parameter `type` isn't used directly but still specifies what type `` is of everywhere else in the method code. So, when you pass the `type` as `MyObject.class`, the method works with a `TypeToken>` and returns a `MyJson`. – Ravi K Thapliyal Dec 31 '15 at 17:03
  • 8
    No, it won't because of type erasure at runtime. Therefore, your function will always return `MyJson` no matter what type you pass as an argument. – hoefling Jan 03 '16 at 13:47
  • @Mr.Yetti Type erasure is a run time thing and has nothing to do with the `type` parameter which is present here to satisfy the generic return type of the method in a statement like `MyJson myJson = doInBackground(...)` **at compile-time**. Remove the `type` and see what compiler error is thrown to understand what I'm saying. – Ravi K Thapliyal Jan 18 '16 at 18:13
  • What exact error do you get? The `doInBackground` function must be inside a generic-typed class, e.g. `class JsonDownloader`, or your code won't compile anyway. Therefore, the `type` parameter doesn't matter. If the method is not inside a generic class, then the signature ` MyJson doInBackground(String json, Void... params)` is sufficient to avoid explicit cast, but that still won't save you from runtime errors. – hoefling Jan 18 '16 at 18:28
  • What I mean with runtime errors is: `MyJson doInBg = doInBackground(Integer.class, "{posts:['text1','text2']}"); double toDouble = doInBg.getPosts().iterator().next().doubleValue(); System.out.println(toDouble);` Your `type` doesn't do anything and your code will fail. – hoefling Jan 18 '16 at 18:33
  • this answer is incorrect and gson will throw a ClassCastException exception. You need to specify the full type every time, you can't make it generic. e.g `Type type = new TypeToken>(){}.getType();` – Yuval Roth Apr 18 '23 at 09:13
24

Have you tried?

gson.create().fromJson(reader, MyJson.class);

EDIT

After reading this post it seems that you use of Type is correct. I believe your issue is the use of T. You must remember that with Java there is type-erasure. This means that at runtime all instances of T are replaced with Object. Therefore at runtime what you are passing GSON is really MyJson<Object>. If you tried this with a concrete class in place of <T> I believe it would work.

Google Gson - deserialize list<class> object? (generic type)

Community
  • 1
  • 1
John B
  • 32,493
  • 6
  • 77
  • 98
  • OK my solution is to send Type as parameter when creating JsonDownloader class, then everything works fine. I didn't know about Type Erasure so thanks for pointing me on it. – VizGhar Aug 23 '13 at 09:39
7

So the above answer didn't work for me, after trial and error that's how my code ended:

public class AbstractListResponse<T> {
    private List<T> result;

    public List<T> getResult() {
        return this.result;
    }
}

The important part here is the method signature, including the '< T >' on the left.

protected <T> AbstractListResponse<T> parseAbstractResponse(String json, TypeToken type) {
    return new GsonBuilder()
            .create()
            .fromJson(json, type.getType());
}

When calling Gson, the method receives the TypeToken of the generic object.

TypeToken<AbstractListResponse<MyDTO>> typeToken = new TypeToken<AbstractListResponse<MyDTO>>() {};
AbstractListResponse<MyDTO> responseBase = parseAbstractResponse(json, typeToken);

And finally the TypeToken can use MyDTO, or even a simple object, just MyDTO.

Davi Alves
  • 1,704
  • 2
  • 19
  • 24
3

For anyone struggling with Kotlin like I did, I've found this way to work

val type = object : TypeToken<MyJson<MyObject>>() { }.type
val response = gson.fromJson<MyJson<MyObject>>(reader, type)

Note that calling a generic function requires the type arguments at the call site after the name of the function (seen here)

tudor
  • 478
  • 4
  • 12
2

If you are using gson 2.8.0 or higher, you can use the following method TypeToken#getParametized((Type rawType, Type... typeArguments))

Example:

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

    GsonBuilder gson = new GsonBuilder();
    Type collectionType = TypeToken.getParameterized(MyJson.class, type).getType();

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

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

I believe this would work in your case.

Credits to this answer.

amp
  • 11,754
  • 18
  • 77
  • 133
1

I used the above answer to figure out more generic way in Kotlin (but you can reuse in java with minor adjustment) I have BaseDB<T> which loads Table<T>, while Table has a List<T>

fun parse(jsonString: String): Table<T> {
    //this gets the class for the type T
    val classT: Class<*> = (javaClass
        .genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<*>
    val type = TypeToken.getParameterized(Table::class.java, classT).type
    return GsonHelper.gson.fromJson<Table<T>>(jsonString, type)
}
M.Baraka
  • 725
  • 1
  • 10
  • 24