How can I use Google GSON to deserialize a JSON array into a a Collection of a generic type?

27,306

Solution 1

Sadly, you can't. The generic type has been erased by the compiler, and there's no way for Gson to know what types you have or what their fields are.

Solution 2

I think you can! From the Gson user guide:

You can solve this problem by specifying the correct parameterized type for your generic type. You can do this by using the TypeToken class.

This means that you could in theory, wrap your Map like so:

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

Or (in my case) I had to wrap a List like so:

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

(I was getting the exception "java.lang.ClassCastException: com.google.gson.internal.StringMap cannot be cast to my.SearchResult".)

Solution 3

I used JsonReader to deserialize json string as follow.

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

    .....
}

You can call above method use below statements (convert Json string to StringReader, and pass StringReader object into the method):

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

    .....
}

Hopefully can works!

Solution 4

The most generic type I can think of in Java is a Map<String, Object> to represent a JSON object and List<Map<String, Object>> to represent an array of JSON objects.

It is very straight forward to parse this using the GSON library (I used version 2.2.4 at the time of writing):

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"));
        }
    }
}
Share:
27,306
Ceasar Bautista
Author by

Ceasar Bautista

Python and web development

Updated on July 09, 2022

Comments

  • Ceasar Bautista
    Ceasar Bautista almost 2 years

    I'm writing some code that is going to be used to retrieve resources from a website. It all sort of looks like this:

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

    So naturally I tried abstracting it using Java generics.

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

    The generic version of the code doesn't quite work though.

    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)
    

    The class in question:

    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
    }
    

    I'm not quite familiar with all the details of Java generics or GSON internals and my search has not been particularly informative. There a bunch of questions here on SO, but most refer to implementing methods like the original one I had. And the GSON docs don't seem to cover this particular case. So again, how can I use Google GSON to deserialize a JSON array into a a Collection of a generic type?