fromjson - Java Type Generic como argumento para GSON
java gson to list (8)
En GSON para obtener una lista de objetos que haces
Gson gson = new Gson();
Type token = new TypeToken<List<MyType>>(){}.getType();
return gson.fromJson(json, token);
Funciona muy bien, pero quiero ir más allá y tener MyType parametrizado para que pueda tener una función común para analizar la lista de objetos con este código
// 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);
Tristemente devuelve una matriz de StringMaps, no el tipo. T se interpreta como otro tipo genérico, no mi tipo. Cualquier solución?
En kotlin uso simple por ejemplo:
Obtener la función de lugares
fun getPlaces<T> (jsonString : String, clazz: Class<T>) : T { val places : T = Gson().fromJson(jsonString,clazz) return places }
Entonces puedes usar como:
val places = getPlaces(Array<Place>::class.java)
Esto ha sido respondido en preguntas anteriores. Básicamente, hay 2 opciones:
- Pase el
Type
desde el sitio que llama. El código de llamada usaráTypeToken
o lo que sea para construirlo. - Construya un
Type
correspondiente al tipo parametrizado usted mismo. Esto requerirá que escribas una clase que implementeParameterizedType
He llevado Raffaele''s enfoque Raffaele''s un paso más allá y he generalizado la clase, de modo que funciona con todas las clases A, donde B es una clase no parametrizada. Puede ser útil para Conjuntos y otras Colecciones.
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;
}
}
La solución Kotlin "ListOfSomething" que funcionó para mí:
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
}
}
Los genéricos funcionan en tiempo de compilación. La razón por la cual los tokens de tipo súper funcionan, se debe a que las clases internas (anónimas) pueden acceder a los argumentos tipo para sus superclases genéricas (superinterfaces), que a su vez se almacenan directamente en los metadatos de bytecode.
Una vez que se compila el archivo fuente .java, obviamente se descarta el parámetro de tipo <T>
. Como no se conoce en tiempo de compilación, no se puede almacenar en bytecode, por lo que se borra y Gson no puede leerlo.
ACTUALIZAR
Después de la respuesta de newacct, traté de implementar lo que él sugirió en su opción 2, es decir, implementar un ParameterizedType
. El código se ve así (aquí hay una test básica):
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;
}
}
el propósito de este código, se debe usar dentro de getFromJsonList()
:
public List<T> fromJsonList(String json, Class<T> klass) {
Gson gson = new Gson();
return gson.fromJson(json, new ListOfSomething<T>(klass));
}
Incluso si la técnica funciona y es realmente muy inteligente (no lo sabía y nunca lo hubiera pensado), este es el logro final:
List<Integer> list = new Factory<Integer>()
.getFromJsonList(text, Integer.class)
en lugar de
List<Integer> list = new Gson().fromJson(text,
new TypeToken<List<Integer>>(){}.getType());
Para mí, todo esto es inútil, incluso si estoy de acuerdo con que TypeToken
haga que el código se vea desagradable: P
Si programamos en kotlin, podemos usar el reified type parameter
en la función en línea
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)
}
}
Y utilícelos a continuación en su código
val arrayList = GenericGson.fromJsonTokenType<ArrayList<Person>>(json)
Sinnce gson 2.8.0
, puede usar TypeToken#getParametized((Type rawType, Type... typeArguments))
para crear el typeToken
, luego getType()
debería hacer el truco.
Por ejemplo:
TypeToken.getParameterized(List.class, myType).getType();
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);
}
Ejemplo:
getList(MyClass[].class, "[{...}]");