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)
);
这显示了对未经检查的分配的警告,但它有效。
我正在构建一个 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)
);
这显示了对未经检查的分配的警告,但它有效。