0

I am trying to use Generics and via Gson, trying to deserialize to type T. Following is my code.

public class Test<T> {
    
    private final Subscriber<T> subscriber;
    Gson gson = new Gson();
    
    public interface Subscriber<T> {
        public void onMessage(T message);
    }
    
    public Test(Subscriber<T> subscriber) {     
        this.subscriber = subscriber;       
    }
    
    public void onResponse(CharSequence data) {
        T responseObj = gson.fromJson(data.toString(), <how to get type of T here>);
        subscriber.onMessage(responseObj);
    }
    
}

As mentioned in the above code in method onResponse, how can I deserialize to object of type T? I know we can pass the type in the constructor and maybe use it. But is there any other cleaner way? If not, I would prefer to leave it to the subscriber and do not use generics here.

Any thoughts? Thanks

hemu
  • 2,951
  • 2
  • 38
  • 62
  • 1
    I don't know Gson but I assume that second parameter is of type `Class`. Assuming you only expect one concrete class for `T` you could pass that to the constructor as well and store it: `Test(Subscriber subscriber, Class type)`. – Thomas Mar 16 '21 at 10:45

2 Answers2

1

Unfortunately this wouldn't be possible due to type erasure. At runtime Java erases the concept of generic types so you can't get a reference to T.class.

What you could do is pass a Class<T> into the constructor of Test<T>. Although it's not the cleanest, it will still be safe.

Henry Twist
  • 4,320
  • 3
  • 12
  • 36
1

As others mentioned, you can't do T.class because of type erasure in runtime and the Java compiler does not have where to take the actual type from. However, the fromJson method also accepts Type as its second parameter, and Type can have various enhancements and implementations. Passing Type instances directly is not that safe, because it is not parameterized and loses type control. Passing Class<T> is a sort of mitigation: it can preserve the type info to some extent, but it loses type parameterization (e.g., Class<List<String>> and Class<List<Map<String, Integer>>> will be simply raw class type List.class -- how to tell Gson which generics to use?). I would do the following:

public final class Test<T> {

    private static final Gson gson = new Gson();

    private final Subscriber<? super T> subscriber;
    private final Type type;

    public interface Subscriber<T> {

        void onMessage(T message);

    }

    private Test(final Subscriber<? super T> subscriber, final Type type) {
        this.subscriber = subscriber;
        this.type = type;
    }

    public Test(final Subscriber<? super T> subscriber, final TypeToken<? extends T> typeToken) {
        this(subscriber, typeToken.getType());
    }

    public void onResponse(final CharSequence data) {
        final T responseObj = gson.fromJson(data.toString(), type);
        subscriber.onMessage(responseObj);
    }

}

In the example above there are two constructors:

  • private one for using some type to deserialize from (private because Type is something Gson needs, we don't expose our internals);
  • public one to get the type from type token (and public because we can guarantee to get the correct Type from type token (assuming it's not broken, sure)).
fluffy
  • 1,185
  • 1
  • 3
  • 14