question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Question: Replacing TypeToken from Guava with Gson ?

See original GitHub issue

I am currently trying to refactor a project to avoid having to use guava as a dependency. I was using it mainly for reflection related features, but in most case I could find an altenative.

However, there is one case left I cannot fix for now: I try to retrieve the class of a generic parameter of the current class.

It works fine using Guava since the Type returned by TokenType can be casted to Class.

Guava: (Class<I>) new com.google.common.reflect.TypeToken<I>(getClass()) {}.getType();

I attempted to substitute this call using the TypeToken from Gson instead, but apparently the information about I is lost and there result of this call is a TypeVariable than is kept as a Class<Object> after executing:

Gson: (Class<I>) com.google.gson.reflect.TypeToken<I>() {}.getType();

As far as I can tell the getClass() parameter is a clue to why context is not kept. When I use Guava with the same signature:

(Class<I>) com.google.gson.reflect.TypeToken<I>() {}.getType();

I get this error message:

java.lang.IllegalStateException: Cannot construct a TypeToken for a type variable. You probably meant to call new TypeToken(getClass()) that can resolve the type variable for you. If you do need to create a TypeToken of a type variable, please use TypeToken.of() instead.

I see Gson also as a (package protected) constructor using a Type as argument.

Is there a way to obtain the class of a Generic Parameter using Gson only ?

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
lyubomyr-shaydarivcommented, Feb 3, 2019

I missed that point 3 months ago, but:

Is there a way to obtain the class of a Generic Parameter using Gson only ?

(IF I understood your question 100% clear) You can’t do this in your case, because you’re trying to obtain the actual class while can obtain a type variable only (at least this is what I can see in your original question). For example, suppose you want to have your code play nice with generics and type safety, and you’re deciding to get rid of Gson TypeToken completely, then you could implement a simple type-safe wrapper:

abstract class T<TYPE>
		implements Supplier<Type> {

	private final Type type;

	protected T() {
		type = resolveType();
	}

	@Override
	public final Type get() {
		return type;
	}

	@Override
	public final boolean equals(final Object o) {
		if ( this == o ) {
			return true;
		}
		if ( o == null || getClass() != o.getClass() ) {
			return false;
		}
		final T<?> that = (T<?>) o;
		return type.equals(that.type);
	}

	@Override
	public final int hashCode() {
		return type.hashCode();
	}

	@Override
	public final String toString() {
		return type.toString();
	}

	private Type resolveType() {
		@SuppressWarnings({ "unchecked", "rawtypes" })
		final Class<T<TYPE>> superclass = (Class) T.class;
		@SuppressWarnings("unchecked")
		final Class<? extends T<TYPE>> thisClass = (Class<T<TYPE>>) getClass();
		final Class<?> actualSuperclass = thisClass.getSuperclass();
		if ( actualSuperclass != superclass ) {
			throw new IllegalArgumentException(thisClass + " must extend " + superclass + " directly but it extends " + actualSuperclass);
		}
		final Type genericSuperclass = thisClass.getGenericSuperclass();
		if ( !(genericSuperclass instanceof ParameterizedType) ) {
			throw new IllegalArgumentException(thisClass + " must parameterize its superclass " + genericSuperclass);
		}
		final ParameterizedType parameterizedGenericSuperclass = (ParameterizedType) genericSuperclass;
		final Type[] actualTypeArguments = parameterizedGenericSuperclass.getActualTypeArguments();
		if ( actualTypeArguments.length != 1 ) {
			throw new AssertionError(actualTypeArguments.length);
		}
		return actualTypeArguments[0];
	}

}

Now suppose you have a quick test like this:

public static <ANY> void main(final String... args) {
	System.out.println(new T<Integer>() {}.get());
	System.out.println(new T<Map<Integer, List<String>>>() {}.get());
	System.out.println(new T<ANY>() {}.get());
}

The code above produces

class java.lang.Integer
java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
ANY

whose classes are class java.lang.Class, class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl, and class sun.reflect.generics.reflectiveObjects.TypeVariableImpl (for example). Note that each of them are implemented differently therefore have different classes. In the case of <ANY> you can’t have an actual type when invoking the method with different type parameters. It won’t work unless you let it know the actual type somehow. For example:

public static void main(final String... args) {
	// ...
	final Integer i1 = thisCanWork(new T<Integer>() {});
	final Map<Integer, List<String>> m1 = thisCanWork(new T<Map<Integer, List<String>>>() {});
	// ...
	final Integer i2 = thisCannotWork();
	final Map<Integer, List<String>> m2 = thisCannotWork();
}

private static <ANY> ANY thisCanWork(final T<ANY> type) {
	System.out.println(type);
	return null; // pretend
}

private static <ANY> ANY thisCannotWork() {
	System.out.println(new T<ANY>() {});
	return null; // pretend
}

This results in

class java.lang.Integer
java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
ANY
ANY

The T implementation is a simple wrapper demonstrating why type tokens can work and how they obtain the type information. Additionally, Class<I> cannot work here because Class only represents a class loaded by a class loader in JVM, these classes cannot be actually parameterized (but can can extend parameterized classes and this makes the type tokens work + and can hold type variable being not able to work as well as the thisCannotWork() method can’t).

Consider type tokens only as a type safe alternative for holding types your application work with (this makes methods like thisCanWork() control what types are used as “in” and “out”), however this is just a wrapper around a Type instance that actually represents a type, not class. In my comment above I marked that Type is the essential part here, and it does not really matter where you can obtain it from: either construct it yourself just implementing proper interfaces, or ask javac to put type information into the subclass’ superclass metadata and then use type token mechanism.

Sorry for a long and vague answer, I hope it can shed some more light on how types and type tokens work.

0reactions
orogercommented, Mar 7, 2019

Thank you for the detailed explanation. This is actually clearer in my mind.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Howto Replace TypeToken from Guava with Gson (Specificaly ...
I am currently trying to refactor a project to avoid having to use guava as a dependency. I was using it mainly for...
Read more >
New TypeToken in common.reflect and Gson - Google Groups
Does it mean there is an option that Gson will (or could) depend on Guava or there will be two independent TypeToken classes...
Read more >
Gson User Guide - Google Sites
You can do this by using the TypeToken class. Type fooType = new TypeToken<Foo<Bar>>() {}. getType(); gson.
Read more >
Gson - How to convert Java object to / from JSON - Mkyong.com
In this tutorial, we will show you how to use Gson to convert Java object to / from JSON. P.S All examples are...
Read more >
TypeToken (Guava: Google Core Libraries for Java 21.0 API)
Wrap a Type obtained via reflection. For example: TypeToken.of(method.getGenericReturnType()) . Capture a generic type with a (usually anonymous) subclass. For ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found