How correctly use Gson to deserialize an object of generic type through a static utility method?

与世无争的帅哥 提交于 2021-01-28 08:57:29

问题


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 a Reader instead of a String, use fromJson(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>> and Class<List<Map<Integer, ?>>> are totally the same List.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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!