Nitzan Tomer
Nitzan Tomer

Reputation: 164129

Gson fails to "understand" generic type when deserializing

I created this memory class:

public class Memory {
    private final Hashtable<String, String> data;
    private final Gson gson;

    public Memory() {
        this.data = new Hashtable<String, String>();
        this.gson = new Gson();
    }

    public <T> void set(String key, List<T> value) {
        this.data.put(key, this.gson.toJson(value));
    }

    public <T> List<T> get(String key, Class<T> cls) {
        Type type = new TypeToken<List<T>>() {}.getType();
        return this.gson.fromJson(this.data.get(key), type);
    }
}


Where I can store lists of generic types in a json and then deserialize them.
But when I try to use it, for example like this:

public class User {
    private int id;
    private String username;

    public User() { }

    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }
}

Memory memory = new Memory();
List<User> users = new ArrayList<User>();
// add users
memory.set("users", users);

// now get the users back
List<User> copy = memory.get("users", User.class);

Gson returns an ArrayList of StringMap instead of Users.
This obviously has something to do with the generics I'm using, but is there a way to bypass it?

Thanks.

Upvotes: 1

Views: 718

Answers (1)

Perception
Perception

Reputation: 80603

The real failure here is highlighted by the obvious inconsistency that Java Generics allow, where a List<User> ends up being filled with instances of com.google.gson.internal.StringMap! But that's a whole other topic.

The immediate problem is that you aren't properly using the type token class. The very point of the token is that you must extend the class with a concrete type - however you are instantiating the class with a method level generic parameter that is validated at compile time then erased (and subsequently unavailable at runtime). But the whole point of type tokens is to retain generic information, so the model is blown.

To be honest, this is a failure in implementation of the token - if you compared the constructor code with, for example, the TypeReference implementation of Jackson, you would see that Jackson actually validates that concrete parameters are available.

31    protected TypeReference()
32    {
33        Type superClass = getClass().getGenericSuperclass();
34        if (superClass instanceof Class<?>) { // sanity check, should never happen
35            throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
36        }
45    }

The easiest solution would be to simply make constructing the type (token) the responsibility of the caller, and pass it in along with the data you wish to store and/or retrieve.

public <T> List<T> get(String key, Type type) {
    return this.gson.fromJson(this.data.get(key), type);
}

public <T> void set(String key, List<T> value, Type type) {
    this.data.put(key, this.gson.toJson(value, type));
}

Upvotes: 2

Related Questions