Google Gson - deserialize list object? (gen

2018-12-31 15:00发布

问题:

I want to transfer a list object via Google Gson, but I don\'t know how to deserialize generic types.

What I tried after looking at this (BalusC\'s answer):

MyClass mc = new Gson().fromJson(result, new List<MyClass>(){}.getClass());

but then I get an error in eclipse saying \"The type new List(){} must implement the inherited abstract method...\" and if I use a quick fix I get a monster of over 20 method stubs.

I am pretty sure that there is an easier solution, but I seem unable to find it!

Edit:

Now I have

Type listType = new TypeToken<List<MyClass>>()
                {
                }.getType();

MyClass mc = new Gson().fromJson(result, listType);

However, I do get the following exception at the \"fromJson\" line:

java.lang.NullPointerException
at org.apache.harmony.luni.lang.reflect.ListOfTypes.length(ListOfTypes.java:47)
at org.apache.harmony.luni.lang.reflect.ImplForType.toString(ImplForType.java:83)
at java.lang.StringBuilder.append(StringBuilder.java:203)
at com.google.gson.JsonDeserializerExceptionWrapper.deserialize(JsonDeserializerExceptionWrapper.java:56)
at com.google.gson.JsonDeserializationVisitor.invokeCustomDeserializer(JsonDeserializationVisitor.java:88)
at com.google.gson.JsonDeserializationVisitor.visitUsingCustomHandler(JsonDeserializationVisitor.java:76)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:106)
at com.google.gson.JsonDeserializationContextDefault.fromJsonArray(JsonDeserializationContextDefault.java:64)
at com.google.gson.JsonDeserializationContextDefault.deserialize(JsonDeserializationContextDefault.java:49)
at com.google.gson.Gson.fromJson(Gson.java:568)
at com.google.gson.Gson.fromJson(Gson.java:515)
at com.google.gson.Gson.fromJson(Gson.java:484)
at com.google.gson.Gson.fromJson(Gson.java:434)

I do catch JsonParseExceptions and \"result\" is not null.

I checked listType with the debugger and got the following:

  • list Type
    • args = ListOfTypes
      • list = null
      • resolvedTypes = Type[ 1 ]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Class (java.util.ArrayList)
    • rawTypeName = \"java.util.ArrayList\"

so it seems the \"getClass\" invocation didn\'t work properly. Any suggestions...?

Edit2: I\'v checked on the Gson User Guide. It mentions a Runtime Exception that should happen during parsing a generic type to Json. I did it \"wrong\" (not shown above), just as in the example, but didn\'t get that exception at all. So I changed the serialization as in the user guide suggested. Didn\'t help, though.

Edit3: Solved, see my answer below.

回答1:

Method to deserialize generic collection:

import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;

...

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> yourClassList = new Gson().fromJson(jsonArray, listType);

Since several people in the comments have mentioned it, here\'s an explanation of how the TypeToken class is being used. The construction new TypeToken<...>() {}.getType() captures a compile-time type (between the < and >) into a runtime java.lang.reflect.Type object. Unlike a Class object, which can only represent a raw (erased) type, the Type object can represent any type in the Java language, including a parameterized instantiation of a generic type.

The TypeToken class itself does not have a public constructor, because you\'re not supposed to construct it directly. Instead, you always construct an anonymous subclass (hence the {}, which is a necessary part of this expression).

Due to type erasure, the TypeToken class is only able to capture types that are fully known at compile time. (That is, you can\'t do new TypeToken<List<T>>() {}.getType() for a type parameter T.)

For more information, see the documentation for the TypeToken class.



回答2:

Another way is to use an array as a type, e.g.:

MyClass[] mcArray = gson.fromJson(jsonString, MyClass[].class);

This way you avoid all the hassle with the Type object, and if you really need a list you can always convert the array to a list by:

List<MyClass> mcList = Arrays.asList(mcArray);

IMHO this is much more readable.

And to make it be an actual list (that can be modified, see limitations of Arrays.asList()) then just do the following:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));


回答3:

Refer to this post. Java Type Generic as Argument for GSON

I have better solution for this. Here\'s the wrapper class for list so the wrapper can store the exactly type of list.

public class ListOfJson<T> implements ParameterizedType
{
  private Class<?> wrapped;

  public ListOfJson(Class<T> wrapper)
  {
    this.wrapped = wrapper;
  }

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

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

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

And then, the code can be simple:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}


回答4:

Wep, another way to achieve the same result. We use it for its readability.

Instead of doing this hard-to-read sentence:

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> list = new Gson().fromJson(jsonArray, listType);

Create a empty class that extends a List of your object:

public class YourClassList extends ArrayList<YourClass> {}

And use it when parsing the JSON:

List<YourClass> list = new Gson().fromJson(jsonArray, YourClassList.class);


回答5:

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

Example:

getList(MyClass[].class, \"[{...}]\");


回答6:

As it answers my original question, I have accepted doc_180\'s answer, but if someone runs into this problem again, I will answer the 2nd half of my question as well:

The NullPointerError I described had nothing to do with the List itself, but with its content!

The \"MyClass\" class didn\'t have a \"no args\" constructor, and neither had its superclass one. Once I added a simple \"MyClass()\" constructor to MyClass and its superclass, everything worked fine, including the List serialization and deserialization as suggested by doc_180.



回答7:

Since Gson 2.8, we can create util function like

public <T> List<T> getList(String jsonArray, Class<T> clazz) {
    Type typeOfT = TypeToken.getParameterized(List.class, clazz).getType();
    return new Gson().fromJson(jsonArray, typeOfT);
}

Example using

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);


回答8:

Here is a solution that works with a dynamically defined type. The trick is creating the proper type of of array using Array.newInstance().

    public static <T> List<T> fromJsonList(String json, Class<T> clazz) {
    Object [] array = (Object[])java.lang.reflect.Array.newInstance(clazz, 0);
    array = gson.fromJson(json, array.getClass());
    List<T> list = new ArrayList<T>();
    for (int i=0 ; i<array.length ; i++)
        list.add(clazz.cast(array[i]));
    return list; 
}


回答9:

For Kotlin simply:

import java.lang.reflect.Type
import com.google.gson.reflect.TypeToken
...
val type = object : TypeToken<ArrayList<T>>() {}.type

or, here is a useful function:

fun <T> buildType(): Type {
    return object : TypeToken<ArrayList<T>>() {}.type
}

Then, to use:

val type = buildType<YourMagicObject>()


回答10:

I want to add for one more possibility. If you don\'t want to use TypeToken and want to convert json objects array to an ArrayList, then you can proceed like this:

If your json structure is like:

{

\"results\": [
    {
        \"a\": 100,
        \"b\": \"value1\",
        \"c\": true
    },
    {
        \"a\": 200,
        \"b\": \"value2\",
        \"c\": false
    },
    {
        \"a\": 300,
        \"b\": \"value3\",
        \"c\": true
    }
]

}

and your class structure is like:

public class ClassName implements Parcelable {

    public ArrayList<InnerClassName> results = new ArrayList<InnerClassName>();
    public static class InnerClassName {
        int a;
        String b;
        boolean c;      
    }
}

then you can parse it like:

Gson gson = new Gson();
final ClassName className = gson.fromJson(data, ClassName.class);
int currentTotal = className.results.size();

Now you can access each element of className object.



回答11:

Refer to example 2 for \'Type\' class understanding of Gson.

Example 1: In this deserilizeResturant we used Employee[] array and get the details

public static void deserializeResturant(){

       String empList =\"[{\\\"name\\\":\\\"Ram\\\",\\\"empId\\\":1},{\\\"name\\\":\\\"Surya\\\",\\\"empId\\\":2},{\\\"name\\\":\\\"Prasants\\\",\\\"empId\\\":3}]\";
       Gson gson = new Gson();
       Employee[] emp = gson.fromJson(empList, Employee[].class);
       int numberOfElementInJson = emp.length();
       System.out.println(\"Total JSON Elements\" + numberOfElementInJson);
       for(Employee e: emp){
           System.out.println(e.getName());
           System.out.println(e.getEmpId());
       }
   }

Example 2:

//Above deserilizeResturant used Employee[] array but what if we need to use List<Employee>
public static void deserializeResturantUsingList(){

    String empList =\"[{\\\"name\\\":\\\"Ram\\\",\\\"empId\\\":1},{\\\"name\\\":\\\"Surya\\\",\\\"empId\\\":2},{\\\"name\\\":\\\"Prasants\\\",\\\"empId\\\":3}]\";
    Gson gson = new Gson();

    // Additionally we need to se the Type then only it accepts List<Employee> which we sent here empTypeList
    Type empTypeList = new TypeToken<ArrayList<Employee>>(){}.getType();


    List<Employee> emp = gson.fromJson(empList, empTypeList);
    int numberOfElementInJson = emp.size();
    System.out.println(\"Total JSON Elements\" + numberOfElementInJson);
    for(Employee e: emp){
        System.out.println(e.getName());
        System.out.println(e.getEmpId());
    }
}


回答12:

I liked the answer from kays1 but I couldn\'t implement it. So I built my own version using his concept.

public class JsonListHelper{
    public static final <T> List<T> getList(String json) throws Exception {
        Gson gson = new GsonBuilder().setDateFormat(\"yyyy-MM-dd HH:mm:ss\").create();
        Type typeOfList = new TypeToken<List<T>>(){}.getType();
        return gson.fromJson(json, typeOfList);
    }
}

Usage:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);


回答13:

In My case @uncaught_exceptions\'s answer didn\'t work, I had to use List.class instead of java.lang.reflect.Type:

String jsonDuplicatedItems = request.getSession().getAttribute(\"jsonDuplicatedItems\").toString();
List<Map.Entry<Product, Integer>> entries = gson.fromJson(jsonDuplicatedItems, List.class);