Deserializing JSON into a class with a generic argument using GSON or Jackson

南楼画角 提交于 2019-12-11 13:44:08

问题


I'm getting responses from my server that look like:

{
  "timestamp" : 1,
  "some other data": "blah",
  "result" : {
     ...
  }
}

for a variety of calls. What I want to do client side is:

class ServerResponse<T> {
    long timestamp;
    T result;

}

and then deserialize that with GSON or Jackson. I've been unable to do so thanks to type erasure. I've cheated that using subclasses like this:

class SpecificResponse extends ServerRequest<SpecificType> {}

but that requires a bunch of useless classes to lie around. Anyone have a better way?

I also need to be able to handle the case of result being an array.


回答1:


The typical solution to type erasure in this case is the type token hack which takes advantage of anonymous classes maintaining superclass information for use with reflection.

Jackson provides the TypeReference type as well as an ObjectMapper#readValue overload to use it.

In your example, you'd use

ServerResponse response = objectMapper.readValue(theJsonSource, new TypeReference<ServerResponse<SpecificType>>() {});

Note that this is not type-safe so you must be careful that the type you try to assign to is compatible with the generic type argument you used in the anonymous class instance creation expression.


As for supporting both single values and arrays in the JSON, you can change your field to be of some Collection type. For example,

List<T> results

Then, enable DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY.

Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array.




回答2:


I support @Pillar solution to use Jackson because it is so straiforward. 2 lines of code...

Here is Gson version that will work the same way, but you will need custom deserializer and a little reflection to achieve this.

public static class CustomDeserializer implements JsonDeserializer<ServerResponse> {
    @Override
    public ServerResponse deserialize(JsonElement je, Type respT,
                          JsonDeserializationContext jdc) throws JsonParseException {

        Type t = (respT instanceof ParameterizedType) ?
                ((ParameterizedType) respT).getActualTypeArguments()[0] :
                Object.class;

        JsonObject jObject = (JsonObject) je;

        ServerResponse serverResponse = new ServerResponse();

        //can add validation and null value check here
        serverResponse.timestamp = jObject.get("timestamp").getAsLong();

        JsonElement dataElement = jObject.get("result");

        if (dataElement != null) {
            if (dataElement.isJsonArray()) {
                JsonArray array = dataElement.getAsJsonArray();

                // can use ((ParameterizedType) respT).getActualTypeArguments()
                // instead of new Type[]{t} 
                // if you 100% sure that you will always provide type
                Type listT = ParameterizedTypeImpl.make(List.class, new Type[]{t}, null);

                serverResponse.result  = jdc.deserialize(array, listT);
            } else if(dataElement.isJsonObject()) {
                serverResponse.result = new ArrayList();
                serverResponse.result.add(jdc.deserialize(dataElement, t));
            }
        }
        return serverResponse;
    }
}

Use case is very simmilar to Jackson:

Gson gson = new GsonBuilder()
           .registerTypeAdapter(ServerResponse.class, new CustomDeserializer())
           .create();

ServerResponse<MyObject> s = gson.fromJson(json, new TypeToken<ServerResponse<MyObject>>(){}.getType())


来源:https://stackoverflow.com/questions/36614097/deserializing-json-into-a-class-with-a-generic-argument-using-gson-or-jackson

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