Gson TypeToken з динамічним типом елемента ArrayList


80

У мене є такий код:

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

Він перетворює рядок JSON в a Listз об'єктів. Але зараз я хочу мати це ArrayListз динамічним типом (не просто myClass), визначеним під час виконання.

В ArrayList«S тип елемента буде визначатися з відображенням .

Я спробував це:

    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....}

1
Це виняток SQLE. Це не має нічого спільного з кодом, який ви опублікували. Покажіть нам код JDBC.
Sotirios Delimanolis

1
@SotiriosDelimanolis Це не так! Я просто хочу, щоб цей TypeToken<ArrayList<T>>()код приймав динамічний тип Arraylist. щось подібне:TypeToken<ArrayList<Class.forName(MyClass)>>
Амін Ш

Відповіді:


49

Запропонований вами синтаксис недійсний. Наступні

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

недійсний, оскільки ви намагаєтесь передати виклик методу там, де очікується ім’я типу.

Наступні

new TypeToken<ArrayList<T>>() 

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

Повертає Тип, що представляє прямий суперклас сутності (класу, інтерфейсу, примітивного типу або void), представленого цим Класом.

Якщо суперклас є параметризованим типом, повернутий об’єкт Type повинен точно відображати фактичні параметри типу, використані у вихідному коді.

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

Typeі ParameterizedTypeє обома інтерфейсами. Ви можете надати приклад власної реалізації.


Я думаю , що це можливо, використовуючи техніку , показану тут: stackoverflow.com/questions/14139437 / ...
Benoit Duffez

2
@BenoitDuffez Обережно. Відповідь, яку ви зв’язали, - це щось інше. Він використовує фактичний Classоб'єкт, тому Gson може дуже добре знати, до якого типу десериалізувати. У запитанні до OP, вони хочуть зробити це без використання Classоб'єкта і лише за допомогою аргументу type, який не доступний у самому методі.
Sotirios Delimanolis

@SotiriosDelimanolis: Ви абсолютно праві. Я думав, що розміщення класу - це прийнятна ціна, щоб це працювало. Але справді відповідна відповідь робить щось інше, ніж те, про що просив ОП. Дякую за роз'яснення.
Бенуа Даффес,


29

Варіант 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);

8

Це спрацювало для мене:

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);
    }
}

Ваш код із `` public <T> '' у методі працює із використанням загальних методів Java, описаних
ʕ ᵔᴥᵔ ʔ

7

Ви можете зробити це за допомогою потужнішого гуави TypeToken:

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

Я вже використовував Guava у своєму проекті. Дякую, працює чудово.
П’яний тато

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


2

Ви насправді можете це зробити. Потрібно спочатку проаналізувати дані на, 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;

цикл try / catch усередині дуже неефективний.
dobrivoje

try / catch усередині або поза циклом не впливає на продуктивність.
Карсон Хольцхаймер

1

За допомогою kotlin ви можете використовувати функції нижче для перетворення (з / в) будь-яких (JsonArray / JsonObject) лише в один рядок без необхідності надсилати TypeToken: -

Перетворити будь-який клас або масив у рядок JSON

inline fun <reified T : Any> T?.json() = this?.let { Gson().toJson(this, T::class.java) }

Приклад для використання:

 val list = listOf("1","2","3")
 val jsonArrayAsString = list.json() 
 //output : ["1","2","3"]

 val model= Foo(name = "example",email = "t@t.com") 
 val jsonObjectAsString = model.json()
//output : {"name" : "example", "email" : "t@t.com"}

Перетворити рядок JSON на будь-який клас або масив

inline fun <reified T : Any> String?.fromJson(): T? = this?.let {
    val type = object : TypeToken<T>() {}.type
    Gson().fromJson(this, type)
}

Приклад для використання:

 val jsonArrayAsString = "[\"1\",\"2\",\"3\"]"
 val list = jsonArrayAsString.fromJson<List<String>>()

 val jsonObjectAsString = "{ "name" : "example", "email" : "t@t.com"}"
 val model : Foo? = jsonObjectAsString.fromJson() 
 //or 
 val model = jsonObjectAsString.fromJson<Foo>() 

0

Повністю робоче рішення:

String json = .... //example: mPrefs.getString("list", "");
ArrayList<YouClassName> myTypes = fromJSonList(json, YouClassName.class);


public <YouClassName> ArrayList<YouClassName> fromJSonList(String json, Class<YouClassName> type) {
        Gson gson = new Gson();
        return gson.fromJson(json, TypeToken.getParameterized(ArrayList.class, type).getType());
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.