【问题标题】:Deserializing JSON into a class with a generic argument using GSON or Jackson使用 GSON 或 Jackson 将 JSON 反序列化为具有通用参数的类
【发布时间】:2016-04-14 04:48:49
【问题描述】:

我从我的服务器收到如下响应:

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

适用于各种通话。我想做的客户端是:

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

}

然后使用 GSON 或 Jackson 对其进行反序列化。由于类型擦除,我一直无法这样做。我欺骗了使用这样的子类:

class SpecificResponse extends ServerRequest<SpecificType> {}

但这需要一堆无用的类。谁有更好的方法?

我还需要能够处理结果为数组的情况。

【问题讨论】:

  • TypeToken 在 Gson,TypeReference 在杰克逊。如果您打算将数组包装在 ServerResponse 中,则该数组会更加困难。
  • 一些框架允许你在客户端请求中指定一个类类型,它具体化了类型参数并使请求返回具体类型。查找采用 Class 参数的请求 api 的变体。
  • @Pillar 我很高兴有一个第二类 ServerArrayRequest,其中变量是 List 结果,如果这更容易的话。我不介意两个,我宁愿没有几十个。
  • Jackson 有一个名为 ACCEPT_SINGLE_VALUE_AS_ARRAY 的功能,可以让你这样做,但你的 Java 代码必须有一个 List&lt;T&gt;。然后将使用单个值填充它。

标签: java generics jackson gson


【解决方案1】:

在这种情况下,类型擦除的典型解决方案是 type token hack,它利用匿名类维护超类信息以供反射使用。

Jackson 提供了 TypeReference 类型以及 ObjectMapper#readValue 重载来使用它。

在你的例子中,你会使用

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

请注意,这不是类型安全的,因此您必须注意您尝试分配的类型与您在匿名类实例创建表达式中使用的泛型类型参数兼容。


至于在 JSON 中同时支持单个值和数组,您可以将字段更改为某种 Collection 类型。例如,

List<T> results

然后,启用DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY

判断是否可以接受强制非数组的功能 (在 JSON 中)值与 Java 集合(数组, java.util.Collection) 类型。如果启用,集合反序列化器将 尝试处理非数组值,就好像它们有“隐式”环绕 JSON 数组。

【讨论】:

  • 不幸的是,它不适用于数组情况,无论是 T 结果包含列表还是 List 结果。无论哪种情况,它都会为结果中的每个元素提供一个列表> ..
  • 我认为我的问题仍然是擦除。我试图在泛型类本身中使用它 - CustomVolleyRequest 其中 Type 是反序列化类型。所以它通过擦除再次成为一个对象。如果我像上课一样传递它....我想我可以做到。
  • 那行得通。好的,所以每个请求调用 1 个锅炉板参数,而不是每种类型的 1 个锅炉位置类。我认为这是一场胜利。是时候进行大规模重构了
【解决方案2】:

我支持@Pillar 使用Jackson 的解决方案,因为它非常直接。 2行代码...

这是 Gson 版本,它的工作方式相同,但您需要自定义反序列化器和一点反射来实现这一点。

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

用例与杰克逊非常相似:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-05
    • 1970-01-01
    • 2016-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多