Reputation: 39853
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>
.
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
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
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