Тип 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;
    }
}