Подтвердить что ты не робот

Gson TypeToken с динамическим типом ArrayList

У меня есть этот код:

Type typeOfObjectsList = new TypeToken<ArrayList<myClass>>() {}.getType();
List<myClass> objectsList = new Gson().fromJson(json, typeOfObjectsList);

Он преобразует строку JSON в объект List. Но теперь я хочу иметь этот ArrayList с динамическим типом (а не только myClass), определенным во время выполнения.

Тип элемента ArrayList будет определен с отражением.

Я пробовал это:

    private <T> Type setModelAndGetCorrespondingList2(Class<T> type) {
        Type typeOfObjectsListNew = new TypeToken<ArrayList<T>>() {}.getType();
        return typeOfObjectsListNew;
    }

Но это не сработает. Это исключение:

java.sql.SQLException: Fail to convert to internal representation: {....my json....}
4b9b3361

Ответ 1

Синтаксис, который вы предлагаете, недействителен. Следующий

new TypeToken<ArrayList<Class.forName(MyClass)>>

неверно, потому что вы пытаетесь передать вызов метода, где ожидается имя типа.

Следующие

new TypeToken<ArrayList<T>>() 

невозможно из-за того, как работают дженерики (стирание типа) и отражение. Весь трюк TypeToken работает, потому что Class#getGenericSuperclass() выполняет следующие

Возвращает тип, представляющий прямой суперкласс объекта (класс, интерфейс, примитивный тип или пустота), представленный этим классом.

Если суперкласс является параметризованным типом, возвращается объект Type должны точно отражать фактические параметры типа, используемые в источнике код.

Другими словами, если он видит ArrayList<T>, то возвращается ParameterizedType, и вы не сможете извлечь значение времени компиляции, которое имела бы переменная типа T.

Type и ParameterizedType - оба интерфейса. Вы можете предоставить экземпляр своей собственной реализации.

Ответ 2

Вариант 1 - реализуйте java.lang.reflect.ParameterizedType самостоятельно и передайте его в Gson.

private static class ListParameterizedType implements ParameterizedType {

    private Type type;

    private ListParameterizedType(Type type) {
        this.type = type;
    }

    @Override
    public Type[] getActualTypeArguments() {
        return new Type[] {type};
    }

    @Override
    public Type getRawType() {
        return ArrayList.class;
    }

    @Override
    public Type getOwnerType() {
        return null;
    }

    // implement equals method too! (as per javadoc)
}

Тогда просто:

Type type = new ListParameterizedType(clazz);
List<T> list = gson.fromJson(json, type);

Обратите внимание, что согласно javadoc метод equals также должен быть реализован.

Вариант 2 - (не делайте этого) повторно использовать gson internal...

Это тоже будет работать, по крайней мере, с Gson 2.2.4.

Type type = com.google.gson.internal.$Gson$Types.newParameterizedTypeWithOwner(null, ArrayList.class, clazz);

Ответ 3

Это сработало для меня:

public <T> List<T> listEntity(Class<T> clazz)
        throws WsIntegracaoException {
    try {
        // Consuming remote method
        String strJson = getService().listEntity(clazz.getName());

        JsonParser parser = new JsonParser();
        JsonArray array = parser.parse(strJson).getAsJsonArray();

        List<T> lst =  new ArrayList<T>();
        for(final JsonElement json: array){
            T entity = GSON.fromJson(json, clazz);
            lst.add(entity);
        }

        return lst;

    } catch (Exception e) {
        throw new WsIntegracaoException(
                "WS method error [listEntity()]", e);
    }
}

Ответ 4

Sinnce gson 2.8.0, вы можете использовать TypeToken#getParametized((Type rawType, Type... typeArguments)) для создания typeToken, тогда getType() должен сделать трюк.

Например:

TypeToken.getParameterized(ArrayList.class, myClass).getType();

Ответ 5

sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl работает. Нет необходимости в пользовательской реализации

Type type = ParameterizedTypeImpl.make(List.class, new Type[]{childrenClazz}, null);
List list = gson.fromJson(json, type);

Может использоваться с картами и любой другой коллекцией:

ParameterizedTypeImpl.make(Map.class, new Type[]{String.class, childrenClazz}, null);

Вот хорошая демонстрация, как вы можете использовать ее в пользовательском десериализаторе: Удаление десериализации ImmutableList с использованием Gson

Ответ 6

Вы можете сделать это с помощью Guava более мощным TypeToken:

private static <T> Type setModelAndGetCorrespondingList2(Class<T> type) {
    return new TypeToken<ArrayList<T>>() {}
            .where(new TypeParameter<T>() {}, type)
            .getType();
}

Ответ 7

Вы можете это сделать. Вам просто нужно сначала проанализировать свои данные в JsonArray, а затем преобразовать каждый объект по отдельности и добавить его в List:

Class<T> dataType;

//...

JsonElement root = jsonParser.parse(json);
List<T> data = new ArrayList<>();
JsonArray rootArray = root.getAsJsonArray();
for (JsonElement json : rootArray) {
    try {
        data.add(gson.fromJson(json, dataType));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
return data;