tynn
tynn

Reputation: 39853

Create TypeToken for type argument of generic super type

I'm implementing a Gson TypeAdapter for two dependend generic models.

interface ModelA<T> {}
interface ModelB<T> extends ModelA<List<T>> {}

For this I need to get the TypeToken and TypeAdapter. By doing

Type type = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeToken<?> token = TypeToken.get(type);
TypeAdapter<?> adapter = gson.getAdapter(token);

I get a type token for both models of any given type AnyType and the correlating adapter. This is what I need for ModelA, but for ModelB I'd need the adapter for List<AnyType> instead, which is the result of using ModelA<List<AnyType>> directly.

Using the interface of the raw type of the original type token passed

token.getRawType().getGenericInterfaces()[0]

I only seem to get the type erased type of List.

How can I combine these two informations to get the generic List<AnyType> type or create it directly? As input it only has the type token for ModelB<AnyType>.

Example

For the given behavior I implemented a simple test showing the differences. I've taken the expected part from the answer by hahn, using the Gson internal non-API class $Gson$Types.

public class TypeTokenValidate {

    public interface ModelA<T> {}
    public interface ModelB<T> extends ModelA<List<T>> {}

    @Test
    public void genericToken() throws Exception {
        TypeToken<ModelB<String>> token = new TypeToken<ModelB<String>>() {};
        ModelB<String> m = new ModelB<String>() {};
        Type expected = $Gson$Types.resolve(ModelB.class, m.getClass(),
                ModelA.class.getTypeParameters()[0]);
        assertEquals(expected, getActual(token));
    }

    private static Type getActual(TypeToken<?> token) {
        Type type = token.getType();
        //type = token.getRawType().getGenericInterfaces()[0];
        type = ((ParameterizedType) type).getActualTypeArguments()[0];
        return TypeToken.get(type).getType();
    }
}

Upvotes: 10

Views: 5205

Answers (2)

hahn
hahn

Reputation: 3658

There is a $Gson$Types that you can utilize to retrieve Type of typed List. For example having:

ModelB<String> m = new ModelB<String>() {};
Type resolve = $Gson$Types.resolve(m.getClass(), m.getClass(), ModelA.class.getTypeParameters()[0]);

would return Type with rawType set to List and with typeArguments of [String]. Thus preserving the types as you need.

and then executing this block

TypeToken<?> token = TypeToken.get(resolve);
TypeAdapter<?> adapter = gson.getAdapter(token);

would result with adaptor of CollectionTypeAdaptorFactory with elementTypeAdapter.type set to class String.

Upvotes: 3

Valentin Ruano
Valentin Ruano

Reputation: 2809

If you use .getType() rather than .getRawType() the information that you are looking for seems to be preserved:

import java.util.List;
import java.lang.reflect.ParameterizedType;
import com.google.gson.reflect.TypeToken;

public class Test  {

   public interface ModelA<T> {};

   public interface ModelB<T> extends ModelA<List<T>> {}

   public static void main(String[] args) {

        // Using java.lang.reflect.*:

        System.out.println(ModelB.class.getGenericInterfaces()[0]);
        // out: Test.Test$ModelA<java.util.List<T>>
        System.out.println(((ParameterizedType)ModelB.class.getGenericInterfaces()[0]).getActualTypeArguments()[0]);
        // out: java.util.List<T>
        System.out.println(((ParameterizedType)((ParameterizedType)ModelB.class.getGenericInterfaces()[0]).getActualTypeArguments()[0]).getActualTypeArguments()[0]);
        // out: T

        // Recovery from a TokenType instance...

        // ... using .getRawType()
        System.out.println(TypeToken.get(ModelB.class.getGenericInterfaces()[0]).getRawType());
        // out: interface Test$ModelA
        System.out.println(TypeToken.get(((ParameterizedType)((ParameterizedType)ModelB.class.getGenericInterfaces()[0]).getActualTypeArguments()[0])).getRawType());
        // out: interface java.util.List

        // ... using .getType()
        System.out.println(TypeToken.get(ModelB.class.getGenericInterfaces()[0]).getType());
        // out: Test$ModelA<java.util.List<T>>
        System.out.println(TypeToken.get(((ParameterizedType)((ParameterizedType)ModelB.class.getGenericInterfaces()[0]).getActualTypeArguments()[0])).getType());
        // out: java.util.List<T>
   }
}

Hopefully you can figure out how to create your TokenAdaptors from there.

Upvotes: 2

Related Questions