Using a generic type with Gson
The simplest approach for what you are attempting is to define the type of class which is to be returned in the method signature itself. You can do so by either passing an instance of 'T' to the method or the Class of the value to be returned. The second approach is the more typical in cases where you expect to generate a return value. Here is an example of this approach using Gson:
public <T> T deserialize(String jsonString, Class<T> clazz) {
GsonBuilder builder = new GsonBuilder();
builder.setDateFormat("MM/dd/yy HH:mm:ss");
Gson gson = builder.create();
return gson.fromJson(jsonString, clazz);
}
Usage:
MyClass mc = deserialize(jsonString, MyClass.class);
Deserializing Generic Types with GSON
You have to specify the type of T
at the time of deserialization. How would your List
of posts
get created if Gson
didn't know what Type
to instantiate? It can't stay T
forever. So, you would provide the type T
as a Class
parameter.
Now assuming, the type of posts
was String
you would deserialize MyJson<String>
as (I've also added a String json
parameter for simplicity; you would read from your reader
as before):
doInBackground(String.class, "{posts: [\"article 1\", \"article 2\"]}");
protected MyJson<T> doInBackground(Class<T> type, String json, Void... params) {
GsonBuilder gson = new GsonBuilder();
Type collectionType = new TypeToken<MyJson<T>>(){}.getType();
MyJson<T> myJson = gson.create().fromJson(json, collectionType);
System.out.println(myJson.getPosts()); // ["article 1", "article 2"]
return myJson;
}
Similarly, to deserialize a MyJson
of Boolean
objects
doInBackground(Boolean.class, "{posts: [true, false]}");
protected MyJson<T> doInBackground(Class<T> type, String json, Void... params) {
GsonBuilder gson = new GsonBuilder();
Type collectionType = new TypeToken<MyJson<T>>(){}.getType();
MyJson<T> myJson = gson.create().fromJson(json, collectionType);
System.out.println(myJson.getPosts()); // [true, false]
return myJson;
}
I've assumed MyJson<T>
for my examples to be as
public class MyJson<T> {
public List<T> posts;
public List<T> getPosts() {
return posts;
}
}
So, if you were looking for to deserialize a List<MyObject>
you would invoke the method as
// assuming no Void parameters were required
MyJson<MyObject> myJson = doInBackground(MyObject.class);
Use Gson library with a generic class as a field of the class to serialize
You can't do this without somehow knowing the type T
at compile time in some manner due to Java's type erasure.
One option for this is the JSON can contain some information specifying the type, e.g.
{
"sendDataType": "MyRequest",
"sendData": {
...
}
}
If you then make SendData generic e.g.
SendData<T> {
private SendDataRequestObject<T> sendData;
}
you can then parse the JSON once to find out the sendDataType
:
SendData<?> genericSendData = new GsonBuilder().create().toJson(requestObject, new TypeToken<SendData<?>>(){});
String sendDataType = genericSendData.sendDataType;
and use that to create a TypeToken
of the right type:
switch(sendDataType) {
case "MyRequest":
return new TypeToken<MyRequest>(){};
}
And then parse the JSON again specifying the generic type now that you know it:
SendData<?> myRequestSendData = new GsonBuilder().create().toJson(requestObject, typeToken);
This works because our switch statement knows the possible types at compile time and can create TypeTokens for them.
GSON using Java generics
Because of type erasure ValidationResult
and ValidationResult<User>
are the same type. There will only ever be one Class
object for that type. Because Gson only knows about ValidationResult
through that Class
object, it has to use a default for the generic type. In this case, it uses a LinkedHashMap
.
The easiest solution is to provide a TypeToken
, which is kind of a hack to get around type erasure (but not really). You can use it like so
gson.fromJson(json, new TypeToken<ValidationResult<User>>() {}.getType());
Through the Type
, Gson has access to the generic type argument. Note how I've hardcoded the type User
as an argument. You have to use an actual type here for Gson to get the actual type. If you use a generic type parameter, Gson will get a generic type parameter as a value and again will not know how to use it. I explain why here.
Another solution is to create a subtype of ValidationResult
like
public class UserValidationResult extends ValidationResult<User> {}
and pass the Class
object of that to your method.
Gson Parsing Generic List From Reflected Field Type
This happens because of how Java generics are implemented: <T>
can only exist in compiler's mind or in class file metadata. Let's take a closer look and perform a brief analysis:
@SuppressWarnings("unchecked")
public static <T> void getListFromJson(Method m, Object target, String json, Class<T> elementType) throws Exception {
m.invoke(target, (List<T>)gson.fromJson(json, new TypeToken<List<T>>(){}.getType()));
}
Class<T> elementType
does not play here at all because of that Java generics restrictions. It's just an attempt to put some generic restrictions for the method, but if you'd remove the parameter, nothing would ever change. Moreover,Class<T>
can hold raw types only, and even if you use unchecked casts to makeClass<T>
hold something likeList<List<List<String>>>
, you'll still get the top-most raw typeList
, and you could remove the parameter (however, hold it for a scenario described below).- Type tokens are based on the fact that JVM allows to store the super class parameterization. You might notice that type tokens are parameterized with type parameters known at compile time:
new TypeToken<String>
ornew TypeToken<List<Integer>>
-- in this case compiler can generate proper superlcass parameterization based on known types. These types are used in type tokens.<T>
is unknown at compile time. Check it in runtime:System.out.println(new TypeToken<T>() {}.getType());
-- the output isT
. What isT
? Just a wildcard, not a real type. - You have to understand what type tokens are for: they are just a convenient way of what could Java support if there were something like
List<Integer>.type
(illegal and not.class
!). Type tokens just analyze their parameterizations are returnType
instances that may beParameterizedType
instances. Moreover, you can create your ownParameterizedType
without type tokens, because these are just interfaces that define some getters. So, in order to build type tokens dynamically:- if you're using Gson 2.8.0, use
TypeToken.getParameterized(List.class, elementType).getType()
; - if you have an older Gson version or you want control it as much as possible, just create and return a
ParameterizedType
instance yourself:
- if you're using Gson 2.8.0, use
new ParameterizedType() {
@Override public Type getRawType() { return List.class; }
@Override public Type[] getActualTypeArguments() { return new Type[]{ elementType }; }
@Override public Type getOwnerType() { return null; }
}
Pretty self-descriptive, isn't it?
- Why does Gson returns
Double
s rather thanLong
s? Having a wildcard of<T>
, Gson uses the default strategy as if the type was completely unknown: doubles are used by default for all numbers since they can keep all standard Java number values. And that's why you get the cast exception when you're dereferencing a list element only (but not lists -- type parameters do not exist for local variables.)
Sorry for such a vague explanation, but summarizing it all up, you only need the following method:
static <T> void getListFromJson(final Method method, final Object target, final String json, final Type elementType)
throws InvocationTargetException, IllegalAccessException {
final List<T> ts = gson.fromJson(json, TypeToken.getParameterized(List.class, elementType).getType());
method.invoke(target, ts);
}
Note that now the elementType
parameter plays the game, the type tokens are used to create a ParameterizedType
instance, and Gson now can use deserializations strategy for the type defined with elementType
(Long
in that place).
Output:
class java.lang.Long
[52881]
52881
Specifying generic type information through a parameter
This isn't going to be possible using a Class<T>
argument, since Class
only supports representing raw types like List
- the type List<GameInfo>
cannot be represented by a Class
object, which is why TypeToken
exists.
Your method would need to take a TypeToken<T>
argument instead and leave it up to the caller to create that argument:
private <T extends Collection<U>, U> T collectionFromJson(String pResponseJson, TypeToken<T> typeToken) {
return (T)new Gson().fromJson(pResponseJson, typeToken.getType());
}
...
TypeToken<List<GameInfo>> typeToken = new TypeToken<List<GameInfo>>() { };
List<GameInfo> lst = collectionFromJson(response, typeToken);
(disclaimer: I only have experience with Java/generics, not GSON)
Related Topics
Why Does Java Code with an Inner Class Generates a Third Someclass$1.Class File
Spring Security's Securitycontextholder: Session or Request Bound
How Can "This" of the Outer Class Be Accessed from an Inner Class
Can Someone Please Explain Me @Mapsid in Hibernate
How Does System.Out.Print() Work
How to Clear Permgen Space Error in Tomcat
How to Concatenate Characters in Java
Getting Nosuchmethoderror:Javax.Servlet.Servletcontext.Getvirtualservername()
What Does Maven Update Project Do in Eclipse
Why Are Wait() and Notify() Declared in Java's Object Class
Configuration Using Annotation @Springbootapplication
Problems Resteasy 3.09 Corsfilter
What Are the Date Formats Available in Simpledateformat Class
Java Native Method Source Code