问题
I've seen a number of other similar looking questions, but I think there's a level of abstraction on top of those that makes the difference. Namely, I have a utility class with a static generic wrapper method to deserialize an object of generic type (unknown at build time):
public final class Utils {
public static final Gson sGson = new Gson();
public static synchronized <T> T fromJson(String doc) {
return sGson.fromJson(doc, new TypeToken<T>(){}.getType());
}
}
A simple class to test it on:
public class TestDocument {
public TestDocument(String test) {
this.test = test;
}
public String test;
}
This works well:
assertEquals(
new TestDocument("test").test,
((TestDocument) Utils.sGson.fromJson(
"{\"test\": \"test\"}",
new TypeToken<TestDocument>(){}.getType())).test);
But what looks like an equivalent way to call this though the static generic utility method does not:
assertEquals(
new TestDocument("test").test,
Utils.<TestDocument>fromJson("{\"test\": \"test\"}").test);
Throws the following exception:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to TestDocument
Is there a way to make it work through the generic method?
回答1:
If it would be possible, Gson
probably would add already this method and it could look like this:
TestDocument document = gson.<TestDocument>fromJson(json);
Method with this signature:
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException
has included JavaDoc
:
This method deserializes the specified Json into an object of the specified class. It is not suitable to use if the specified class is a generic type since it will not have the generic type information because of the Type Erasure feature of Java. Therefore, this method should not be used if the desired type is a generic type. Note that this method works fine if the any of the fields of the specified object are generics, just the object itself should not be a generic type. For the cases when the object is of generic type, invoke
fromJson(String, Type)
. If you have the Json in aReader
instead of aString
, usefromJson(Reader, Class)
instead.
Even second parameter name is classOfT
has meaning the class of T
.
回答2:
There are a few type usage hints:
- Using
<T>
without passing an actual type is a hoax due to the generic types erasure. - Passing the type as
Class<T>
is not a very good idea because###.class
merely represents a class loaded by JVM (except the primitive types). Having that,Class<List<String>>
andClass<List<Map<Integer, ?>>>
are totally the sameList.class
losing type parameterization therefore making Gson (de)serialize work without proper types in mind (LinkedHashTreeMap, for example, is a good example if I remember). - Gson mostly works with
Type
that is a super type interface for any type that can be represented by the Java type system (including classes,ParameterizedType
, etc). See https://google.github.io/gson/apidocs/com/google/gson/Gson.html#fromJson-java.lang.String-java.lang.reflect.Type- TypeToken
is a good example of a generic type holder in Java, including it produces proper type information depending on how it was build. It can be used to make your method type safe:public static <T> T fromJson(String doc, TypeToken<? extends T> typeToken) { return sGson.fromJson(doc, typeToken.getType()); }
. Type tokens can be cached into public (yes) static final fields holding real parameterization due being immutable and thread-safe across threads.
Bonus:
- No
synchronized
is necessary there:Gson
instances are thread-safe too.
回答3:
It looks like it is impossible to do this in Java without passing the actual type explicitly so that it is known at build time.
Here's one solution:
public static synchronized <T> T fromJson(String doc, Class<T> type) {
return sGson.fromJson(doc, type);
}
Then the test passes:
assertEquals((new TestDocument("test").test), Utils.<TestDocument>fromJson("{\"test\": \"test\"}", TestDocument.class).test);
This looks like it can be done in a much simpler and more elegant way in this particular (intentionally oversimplified) example, but may be the only option when it's a part of a larger, more complex scenario.
来源:https://stackoverflow.com/questions/54916191/how-correctly-use-gson-to-deserialize-an-object-of-generic-type-through-a-static