可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
In GSON to get a list of objects you do
Gson gson = new Gson();
Type token = new TypeToken<List<MyType>>(){}.getType();
return gson.fromJson(json, token);
It works great, but I want to go further and have MyType parametrized so I can have a common function to parse list of objects with this code
// the common function
public <T> List<T> fromJSonList(String json, Class<T> type) {
Gson gson = new Gson();
Type collectionType = new TypeToken<List<T>>(){}.getType();
return gson.fromJson(json, collectionType);
}
// the call
List<MyType> myTypes = parser.fromJSonList(jsonString, MyType.class);
Sadly returns an array of StringMaps, not the type. T is being interpreted as another generic type, not my type. Any workaround ?
回答1:
Generics work at compile-time. The reason super-type tokens work, is because (anonymous) inner classes can access the type arguments to their generic superclasses (superinterfaces), which in turn are stored directly in the bytecode metadata.
Once your .java source file is compiled, the type parameter <T>
is obviously thrown away. Since it is not known at compile time, it cannot be stored in bytecode, so it's erased and Gson can't read it.
UPDATE
After newacct's answer, I tried to implement what he suggested in his option 2, ie implementing a ParameterizedType
. The code looks like this (here is a basic test):
class ListOfSomething<X> implements ParameterizedType {
private Class<?> wrapped;
public ListOfSomething(Class<X> wrapped) {
this.wrapped = wrapped;
}
public Type[] getActualTypeArguments() {
return new Type[] {wrapped};
}
public Type getRawType() {
return List.class;
}
public Type getOwnerType() {
return null;
}
}
the purpose of this code, is to be used inside getFromJsonList()
:
public List<T> fromJsonList(String json, Class<T> klass) {
Gson gson = new Gson();
return gson.fromJson(json, new ListOfSomething<T>(klass));
}
Even if the technique works and is indeed very clever (I didn't know it and I would have never thinked of it), this is the final accomplishment:
List<Integer> list = new Factory<Integer>()
.getFromJsonList(text, Integer.class)
instead of
List<Integer> list = new Gson().fromJson(text,
new TypeToken<List<Integer>>(){}.getType());
To me, all this wrapping in useless, even if I agree that TypeToken
s make the code look nasty :P
回答2:
Since gson 2.8.0
, you can use TypeToken#getParametized((Type rawType, Type... typeArguments))
to create the typeToken
, then getType()
should do the trick.
For example:
TypeToken.getParameterized(List.class, myType).getType();
回答3:
public static final <T> List<T> getList(final Class<T[]> clazz, final String json)
{
final T[] jsonToObject = new Gson().fromJson(json, clazz);
return Arrays.asList(jsonToObject);
}
Example:
getList(MyClass[].class, "[{...}]");
回答4:
I've taken Raffaele's approach one step further and generified the class, so that it works with every class A, where B is a non-parameterized class. Might be useful for Sets and other Collections.
public class GenericOf<X, Y> implements ParameterizedType {
private final Class<X> container;
private final Class<Y> wrapped;
public GenericOf(Class<X> container, Class<Y> wrapped) {
this.container = container;
this.wrapped = wrapped;
}
public Type[] getActualTypeArguments() {
return new Type[]{wrapped};
}
public Type getRawType() {
return container;
}
public Type getOwnerType() {
return null;
}
}
回答5:
Here is the full code base on great answer from @oldergod
public <T> List<T> fromJSonList(String json, Class<T> myType) {
Gson gson = new Gson();
Type collectionType = TypeToken.getParameterized(List.class, myType).getType();
return gson.fromJson(json, collectionType);
}
Using
List<MyType> myTypes = parser.fromJSonList(jsonString, MyType.class);
Hope it help
回答6:
This has been answered in previous questions. Basically, there are 2 options:
- Pass the
Type
in from the calling site. The calling code will use TypeToken
or whatever to construct it.
- Construct a
Type
corresponding to the parameterized type yourself. This will require you to write a class that implements ParameterizedType
回答7:
If programming in kotlin, we can use reified type parameter
in inline function
class GenericGson {
companion object {
inline fun <reified T : Any> Gson.fromJsonTokenType(jsonString: String): T {
val type = object : TypeToken<T>() {}.type
return this.fromJson(jsonString, type)
}
inline fun <reified T : Any> Gson.fromJsonType(jsonString: String): T = this.fromJson(jsonString, T::class.java)
inline fun <reified T : Any> fromJsonTokenType(jsonString: String): T = Gson().fromJsonTokenType(jsonString)
inline fun <reified T : Any> fromJsonType(jsonString: String): T = Gson().fromJsonType(jsonString)
}
}
And use like below in your code
val arrayList = GenericGson.fromJsonTokenType<ArrayList<Person>>(json)
回答8:
Kotlin "ListOfSomething" solution that worked for me:
fun <T: Any> getGsonList(json: String, kclass: KClass<T>) : List<T> {
return getGsonInstance().fromJson<List<T>>(json, ListOfSomething<T>(kclass.java))
}
internal class ListOfSomething<X>(wrapped: Class<X>) : ParameterizedType {
private val wrapped: Class<*>
init {
this.wrapped = wrapped
}
override fun getActualTypeArguments(): Array<Type> {
return arrayOf<Type>(wrapped)
}
override fun getRawType(): Type {
return ArrayList::class.java
}
override fun getOwnerType(): Type? {
return null
}
}
回答9:
In kotlin simple use for example:
Get places function
fun getPlaces<T> (jsonString : String, clazz: Class<T>) : T {
val places : T = Gson().fromJson(jsonString,clazz)
return places
}
Then you can use as:
val places = getPlaces(Array<Place>::class.java)
回答10:
public static <T> T getObject(String gsonStr) {
Gson gson = new GsonBuilder()
.setLenient()
.create();
Type collectionType = new TypeToken< T>(){}.getType();
return gson.fromJson(gsonStr,
collectionType);
}
When use:
Class1 class1= getObject(jsonStr);
回答11:
This work for everything.
e.g. map which has a key and value generic.
CustomType type = new CustomType(Map.class, String.class, Integer.class);
So no more TokenType.
class CustomType implements ParameterizedType {
private final Class<?> container;
private final Class<?>[] wrapped;
@Contract(pure = true)
public CustomType(Class<?> container, Class<?>... wrapped) {
this.container = container;
this.wrapped = wrapped;
}
@Override
public Type[] getActualTypeArguments() {
return this.wrapped;
}
@Override
public Type getRawType() {
return this.container;
}
@Override
public Type getOwnerType() {
return null;
}
}