Как я могу использовать Google GSON для десериализации массива JSON в виде коллекции общего типа?

Я пишу код, который будет использоваться для извлечения ресурсов с веб-сайта. Все выглядит так:

public Collection<Project> getProjects() {
        String json = getJsonData(methods.get(Project.class)); //Gets a json list, ie [1, 2, 3, 4]
        Gson gson = new Gson();
        Type collectionType = new TypeToken<Collection<Project>>() {}.getType();
        return gson.fromJson(json, collectionType);
    }

Поэтому, естественно, я попытался абстрагировать его с помощью дженериков Java.

/*
     * Deserialize a json list and return a collection of the given type.
     * 
     * Example usage: getData(AccountQuota.class) -> Collection<AccountQuota>
     */
    @SuppressWarnings("unchecked")
    public <T> Collection<T> getData(Class<T> cls) {
        String json = getJsonData(methods.get(cls)); //Gets a json list, ie [1, 2, 3, 4]
        Gson gson = new Gson();
        Type collectionType = new TypeToken<Collection<T>>(){}.getType();
        return (Collection<T>) gson.fromJson(json, collectionType);
}

Однако общая версия кода не работает.

public void testGetItemFromGetData() throws UserLoginError, ServerLoginError {
            Map<String,String> userData = GobblerAuthenticator.authenticate("[email protected]", "mypassword");
            String client_key = userData.get("client_key");
            GobblerClient gobblerClient = new GobblerClient(client_key);
            ArrayList<Project> machines = new ArrayList<Project>();
            machines.addAll(gobblerClient.getData(Project.class));
            assertTrue(machines.get(0).getClass() == Project.class);
            Log.i("Machine", gobblerClient.getData(Project.class).toString());
        }


java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.gobbler.synchronization.Machine
at com.gobblertest.GobblerClientTest.testGetItemFromGetData(GobblerClientTest.java:53)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:545)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1551)

Этот класс:

import java.util.Map;


public class Project {
    private int total_bytes_stored;
    private String name;
    private String user_data_guid;
    private int seqnum;
    private String guid;
    private Map current_checkpoint;
    private Map<String, String> upload_folder;
    // TODO: schema_version
    private boolean deleted;
    // TODO: download_folders

    public Project() {} // No args constructor used for GSON
}

Я не совсем знаком со всеми подробностями Java-дженериков или внутренних компонентов GSON, и мой поиск не был особенно информативным. Там здесь есть куча вопросов, но большинство из них относится к методам реализации, таким как оригинал, который у меня был. И документы GSON, похоже, не охватывают этот конкретный случай. Итак, как я могу использовать Google GSON для десериализации массива JSON в коллекцию родового типа?

Ответ 1

К сожалению, вы не можете. Генерический тип был удалён компилятором, и GSP не знает, какие у вас есть или какие у них есть поля.

Ответ 2

Я думаю, вы можете! Из руководства пользователя Gson:

Вы можете решить эту проблему, указав правильный параметризованный тип для своего общего типа. Вы можете сделать это, используя класс TypeToken.

Это означает, что вы могли бы теоретически обернуть свою карту следующим образом:

Type t = new TypeToken<Map<String,Machine>>() {}.getType();
Map<String,Machine> map = (Map<String,Machine>) new Gson().fromJson("json", t);

Или (в моем случае) мне пришлось обернуть List следующим образом:

Type t = new TypeToken<List<SearchResult>>() {}.getType();
List<SearchResult> list = (List<SearchResult>) new Gson().fromJson("json", t);

for (SearchResult r : list) {
  System.out.println(r);
}

(я получал исключение "java.lang.ClassCastException: com.google.gson.internal.StringMap нельзя отнести к my.SearchResult".)

Ответ 3

Я использовал JsonReader для десериализации строки json, как показано ниже.

public class JSONReader<T> {

    .....

    private Class<T> persistentClass;

    public Class<T> getPersistentClass() {
        if (persistentClass == null) {
            this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        }
        return persistentClass;
    }

    public List<T> read(Reader reader) throws IOException {
        JsonReader jsonReader = new JsonReader(reader);
        List<T> objs = new ArrayList<T>();
        jsonReader.beginArray();      
        while (jsonReader.hasNext()) {
            T obj = (new Gson()).fromJson(jsonReader, getPersistentClass());
            if (logger.isFine()) {
                logger.fine(obj.toString());
            }
            objs.add(obj);
        }
        jsonReader.endArray();
        jsonReader.close();
        return objs;
    }

    .....
}

Вы можете вызывать выше метод использования ниже операторов (преобразовать строку Json в StringReader и передать объект StringReader в метод):

public class Test extends JSONReader<MyClass> {

    .....

    public List<MyClass> parse(String jsonString) throws IOException {
        StringReader strReader = new StringReader(jsonString);
        List<MyClass> objs = read(strReader);
        return objs;
    }

    .....
}

Мы надеемся, что это сработает!

Ответ 4

Самый общий тип, который я могу представить в Java, - это Map<String, Object> для представления объекта JSON и List<Map<String, Object>> для представления массива объектов JSON.

Очень просто проанализировать это с помощью библиотеки GSON (я использовал версию 2.2.4 на момент написания):

import com.google.gson.Gson;

import java.util.List;
import java.util.Map;

public class Test {
    public static void main(String[] args) {
        String json = "{\"name\":\"a name\"}";
        Gson GSON = new Gson();
        Map<String, Object> parsed = GSON.fromJson(json, Map.class);
        System.out.println(parsed.get("name"));

        String jsonList = "[{\"name\":\"a name\"}, {\"name\":\"a name2\"}]";
        List<Map<String, Object>> parsedList = GSON.fromJson(jsonList, List.class);
        for (Map<String, Object> parsedItem : parsedList) {
            System.out.println(parsedItem.get("name"));
        }
    }
}