Jackson 解码通用类型

Jackson Decoding Generic Types

我正在构建一个 client/server 应用程序,并将请求和响应消息封装在通用 Java classes 中,如下所示:

@SuperBuilder
@Jacksonized
@Getter
@Setter
public class AppResponse<T extends AppResponse.Results> {
    public static final String SUCCESS_ATTRIBUTE = "success";
    public static final String MESSAGE_ATTRIBUTE = "message";
    public static final String REQUEST_ID_ATTRIBUTE = AppRequest.REQUEST_ID_ATTRIBUTE;
    public static final String RESULTS_ATTRIBUTE = "results";

    @JsonProperty(SUCCESS_ATTRIBUTE)
    boolean success;

    @JsonProperty(MESSAGE_ATTRIBUTE)
    String message;

    @JsonProperty(REQUEST_ID_ATTRIBUTE)
    String requestID;

    @JsonProperty(RESULTS_ATTRIBUTE)
    T results;

    @SuperBuilder
    @Jacksonized
    @Getter
    @Setter
    public static class Results {

    }
}

然后我有一个 class 扩展 AppResponse.Results 用于每个可以通过网络发送的命令响应数据包。例如:

@SuperBuilder
@Getter
@Setter
@Jacksonized
public class AppCreateForumPostResults extends AppResponse.Results {
    @JsonProperty("id")
    UUID id;
}

因此,当服务器使用 Jackson 将其编码为 JSON 时,我得到一个 JSON 文档,如下所示:

{
  "success" : true,
  "requestID" : "eL55P4Sz",
  "results" : {
    "id" : "0b48d389-2407-43c4-ad1b-ab52599c2d7f"
  }
}

当客户端应用程序收到此消息时,它首先将其解码为 ObjectNode:

objectNode = (ObjectNode) AppObjectMapper.OBJECT_MAPPER.readTree(message);

然后将其解码为泛型对象,因为它还不知道泛型的具体类型是什么:

JavaType genericType = AppObjectMapper.OBJECT_MAPPER.getTypeFactory().constructParametricType(
    AppResponse.class,
    AppResponse.Results.class
);

AppResponse<AppResponse.Results> genericResponse = AppObjectMapper.OBJECT_MAPPER.treeToValue(
    objectNode,
    genericType
);

然后它查询已发送的 requestID 的 HashMap 以找出它应该期望什么样的结果 class。 HashMap 定义为:

private final Map<String, CallbackEntry<? extends AppResponse.Results>> callbacks = new HashMap<>();

并且 CallbackEntry 定义为:

@Builder
@Value
public static class CallbackEntry<T extends AppResponse.Results> {
    Callback<T> method;
    Class<T> resultsClass;
}

最后回调定义为:

public interface Callback<T extends AppResponse.Results> {
    void handleResponse(AppResponse<T> results);
}

所以一旦我们在 HashMap 中找到了 requestID,我们就知道结果包含的具体 class 内容,我们可以对其进行解码:

CallbackEntry<? extends AppResponse.Results> callbackEntry = callbacks.get(genericResponse.getRequestID());

callbacks.remove(genericResponse.getRequestID());

JavaType concreteType = AppObjectMapper.OBJECT_MAPPER.getTypeFactory().constructParametricType(
    AppResponse.class,
    callbackEntry.resultsClass
);

AppResponse<? extends AppResponse.Results> concreteResponse = AppObjectMapper.OBJECT_MAPPER.treeToValue(
    objectNode,
    concreteType
);

到目前为止,还不错。这一切都按预期工作。如果我打印出 concreteResponse 对象,它就会包含正确的数据。但是,当我去实际调用回调方法时,出现错误。调用看起来像:

callbackEntry.method.handleResponse(concreteResponse);

我在那行代码中得到的错误是:

[127,49] incompatible types: com.whatever.app.common.AppResponse<capture#1 of ? extends com.whatever.app.common.AppResponse.Results> cannot be converted to com.whatever.app.common.AppResponse<capture#2 of ? extends com.whatever.app.common.AppResponse.Results>

错误消息让我有点困惑。我猜它必须以某种方式与处理泛型的方式有关,但我只是无法弄清楚正确的咒语是什么。我做了很多谷歌搜索,到处都发现了类似的错误,但它们通常是由于有人使用 ?而不是 T,(我认为)这里不是这种情况。

那么,我错过了什么?

非常感谢任何帮助!

我能够通过像这样转换函数参数来让它工作:

callbackEntry.method.handleResponse(
    (concreteResponse.getClass()).cast(concreteResponse)
);

这显示了对未经检查的分配的警告,但它有效。