Тип Java Generic как аргумент для GSON
В GSON для получения списка объектов, которые вы делаете
Gson gson = new Gson();
Type token = new TypeToken<List<MyType>>(){}.getType();
return gson.fromJson(json, token);
Он отлично работает, но я хочу пойти дальше и настроить MyType, чтобы у меня была общая функция для анализа списка объектов с помощью этого кода.
// 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);
Печально возвращает массив StringMaps, а не тип. T интерпретируется как другой общий тип, а не мой тип. Любое обходное решение?
Ответы
Ответ 1
Работа генераторов во время компиляции. Причина, по которой работают маркеры супертипа, заключается в том, что (анонимные) внутренние классы могут обращаться к аргументам типа к их общим суперклассам (суперинтерфейсам), которые, в свою очередь, хранятся непосредственно в метаданных байткода.
Как только ваш исходный файл .java скомпилирован, параметр типа <T>
явно выбрасывается. Поскольку он не известен во время компиляции, он не может быть сохранен в байт-коде, поэтому он стирается, и Gson не может его прочитать.
UPDATE
После ответа newacct я попытался реализовать то, что он предложил в своем варианте 2, т.е. реализовать ParameterizedType
. Код выглядит следующим образом (вот базовый 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;
}
}
цель этого кода должна использоваться внутри getFromJsonList()
:
public List<T> fromJsonList(String json, Class<T> klass) {
Gson gson = new Gson();
return gson.fromJson(json, new ListOfSomething<T>(klass));
}
Даже если техника работает и действительно очень умна (я этого не знал, и я бы никогда об этом не думал), это окончательное достижение:
List<Integer> list = new Factory<Integer>()
.getFromJsonList(text, Integer.class)
вместо
List<Integer> list = new Gson().fromJson(text,
new TypeToken<List<Integer>>(){}.getType());
Для меня все это обертывание бесполезно, даже если я соглашусь, что TypeToken
делает код выглядящим противным: P
Ответ 2
Начиная с gson 2.8.0
, вы можете использовать TypeToken#getParametized((Type rawType, Type... typeArguments))
для создания typeToken
, тогда getType()
должна сделать getType()
дело.
Например:
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);
}
Пример:
getList(MyClass[].class, "[{...}]");
Ответ 4
Я принял подход Раффаэля еще на один шаг и обобщил класс, чтобы он работал с каждым классом A, где B - непараметрированный класс. Может быть полезно для наборов и других коллекций.
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
Вот полная база кода на отличный ответ от @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);
}
С помощью
List<MyType> myTypes = parser.fromJSonList(jsonString, MyType.class);
Надеюсь, это поможет
Ответ 6
Об этом ответили в предыдущих вопросах. В принципе, есть 2 варианта:
- Передайте
Type
с вызывающего сайта. Вызывающий код будет использовать TypeToken
или что-то другое для его создания.
- Создайте a
Type
, соответствующий параметризованному типу самостоятельно. Это потребует от вас написать класс, который реализует ParameterizedType
Ответ 7
Если программирование в kotlin, мы можем использовать reified type parameter
в встроенной функции
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)
}
}
И используйте, как показано ниже в вашем коде
val arrayList = GenericGson.fromJsonTokenType<ArrayList<Person>>(json)
Ответ 8
Решение Kotlin "ListOfSomething", которое работало для меня:
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
В kotlin простое использование, например:
Функция "Получить места"
fun getPlaces<T> (jsonString: String, clazz: Class<T>): T { val places: T = Gson().fromJson(jsonString,clazz) return places }
Тогда вы можете использовать как:
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);
}
При использовании:
Class1 class1= getObject(jsonStr);
Ответ 11
Это работа для всего.
например карта с общим ключом и значением.
CustomType type = new CustomType(Map.class, String.class, Integer.class);
Так что больше нет 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;
}
}