每当我收到错误时,错误正文如下:
[
{
"errorCode": 10001,
"resource": null,
"resourceId": null,
"field": null,
"parameter": null,
"header": null,
"allowedValues": null,
"maxLength": null,
"minLength": null
}
]
错误正文是一个数组。我有许多API方法成功的不同主体,但错误数组响应是标准化的。我尝试过很多事情
使用泛型类型成功响应和错误响应数组制作包装类,并为此创建反序列化器,但我无法从类型变量和参数化类中反序列化。
制作了一个ErrorDeserializer,但我不知道如何让Retrofit将它用于错误响应。
我绝对可以在每次回调时为所有api方法序列化原始字符串,但我有很多这样的,我需要通用的解决方案。如果我没有正确解释自己,请询问。
我会添加我尝试的例子(但它们将不完整):
响应换行类:
public class ResponseWrap<T> {
@Nullable
private final T response;
@Nullable
private final List<ErrorResponse> errorResponses;
public ResponseWrap(@Nullable T response, @Nullable List<ErrorResponse> errorResponses) {
this.response = response;
this.errorResponses = errorResponses;
}
}
错误响应类:
public class ErrorResponse {
private int errorCode;
private String resource;
private String resourceId;
private String field;
private String parameter;
private String header;
private String allowedValues;
private int maxLength;
private int minLength;
// getters and setters
}
错误解串器:
public class ErrorDeserializer implements JsonDeserializer<ArrayList<ErrorResponse>> {
@Override
public ArrayList<ErrorResponse> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
Gson gson = new Gson();
Type listType = new TypeToken<ArrayList<ErrorResponse>>(){}.getType();
ArrayList<ErrorResponse> list = new Gson().fromJson(json, listType);
final JsonArray jsonArray = json.getAsJsonArray();
for (int i = 0; i < jsonArray.size(); i++) {
ErrorResponse error = new ErrorResponse();
JsonObject jsonObject = jsonArray.get(i).getAsJsonObject();
error.setErrorCode(jsonObject.get("errorCode").getAsInt());
error.setResource(jsonObject.get("resource").getAsString());
error.setResourceId(jsonObject.get("resourceId").getAsString());
error.setField(jsonObject.get("field").getAsString());
error.setParameter(jsonObject.get("parameter").getAsString());
error.setHeader(jsonObject.get("header").getAsString());
error.setAllowedValues(jsonObject.get("allowedValues").getAsString());
error.setMaxLength(jsonObject.get("maxLength").getAsInt());
error.setMinLength(jsonObject.get("minLength").getAsInt());
list.add(error);
}
return list;
}
}
响应包装反序列化器 - 它不能正常工作,2个错误:
List error = new Gson()。fromJson(jsonObject.getAsJsonObject(“error”),ArrayList.class); //无法从参数化类
T success = new Gson()。fromJson(jsonObject,T.class); //无法从类型变量中选择
public class ResponseWrapDeserializer<T> implements JsonDeserializer<ResponseWrap<T>> {
@Override
public ResponseWrap<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// Get JsonObject
final JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("error")) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(typeOfT, new ErrorDeserializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create();
List<ErrorResponse> error = new Gson().fromJson(jsonObject.getAsJsonObject("error"), ArrayList<ErrorResponse>.class);
return new ResponseWrap<T>(null, error);
} else {
T success = new Gson().fromJson(jsonObject, T.class);
return new ResponseWrap<T>(success, null);
}
}
}
这个想法就是像这样使用它们:
@POST("Login")
Call<ResponseWrap<AccessTokenResponse>> Login(@Body LoginRequest request);
但由于上述原因,我不能。
问题是:如何使用Retrofit2以通用方式处理数组中的错误响应?
答案 0 :(得分:1)
你不能写T.class
- 这在Java中是非法的。为了克服这个限制,您必须以某种方式自己传递Type
实例,或者从Gson提供的内容中解析泛型类型参数。在第一种情况下,您需要打十几个JSON反序列化器来绑定各种ResponseWrap<T>
参数化;而在第二种情况下,您可以自己简单地解析实际的类型参数。在调用站点,您可以使用TypeToken
s - 一种特殊的Gson机制,通过类型参数化定义类型参数。另请注意,您不必实例化内部Gson
实例:这可能相对昂贵(特别是按顺序)并且不尊重当前反序列化器绑定的Gson
配置 - 使用JsonDeserializationContext
因为它可以满足您的所有需求(下游类型适配器除外)。
以下JSON反序列化程序使用第二种方法,因为我发现它更方便。
final class ResponseWrapJsonDeserializer<T>
implements JsonDeserializer<ResponseWrap<T>> {
// This deserializer holds no state, so we can hide its instantiation details away
private static final JsonDeserializer<ResponseWrap<Object>> responseWrapJsonDeserializer = new ResponseWrapJsonDeserializer<>();
// Type instances from TypeToken seems to be fully immutable and can be treated as value types, thus we can make them static final to re-use (it's safe)
private static final Type errorResponseListType = new TypeToken<List<ErrorResponse>>() {
}.getType();
private ResponseWrapJsonDeserializer() {
}
// Just cheating the call site: we always return the same instance if the call site requests for a specially typed deserializer (it's always the same instance however, this is just how Java generics work)
static <T> JsonDeserializer<ResponseWrap<T>> getResponseWrapJsonDeserializer() {
@SuppressWarnings({ "rawtypes", "unchecked" })
final JsonDeserializer<ResponseWrap<T>> cast = (JsonDeserializer) responseWrapJsonDeserializer;
return cast;
}
@Override
public ResponseWrap<T> deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
// Checking if jsonElement looks like an error (I'm not sure if it's possible to check HTTP statuses delegating them to request/response converters in Retrofit)
if ( isError(jsonElement) ) {
final List<ErrorResponse> errorResponses = context.deserialize(jsonElement, errorResponseListType);
return new ResponseWrap<>(null, errorResponses);
}
// If it does not look an error, then:
// * resolve what's the actual T in the given ResponseWrap<T>
// * deserialize the JSON tree as an instance of T -- it's like we're stripping the wrapper and then instantiate the wrap due to our rules
final T response = context.deserialize(jsonElement, resolveTypeParameter0(type));
return new ResponseWrap<>(response, null);
}
private static Type resolveTypeParameter0(final Type type) {
// The given type does not have parameterization?
if ( !(type instanceof ParameterizedType) ) {
// Then it's raw, simply <Object> or <?>
return Object.class;
}
// If it's parameterized, let's take it's first parameter as ResponseWrap is known to a have a single type parameter only
return ((ParameterizedType) type).getActualTypeArguments()[0];
}
// Some AI party here, he-he
private static boolean isError(final JsonElement jsonElement) {
if ( !jsonElement.isJsonArray() ) {
return false;
}
final JsonArray jsonArray = jsonElement.getAsJsonArray();
for ( final JsonElement innerJsonElement : jsonArray ) {
if ( !innerJsonElement.isJsonObject() ) {
return false;
}
final JsonObject innerJsonObject = innerJsonElement.getAsJsonObject();
final boolean looksLikeErrorObject = innerJsonObject.has("errorCode");
if ( !looksLikeErrorObject ) {
return false;
}
}
return true;
}
}
接下来,注册Gson
实例的反序列化器:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(ResponseWrap.class, getResponseWrapJsonDeserializer())
.create();
用
进行测试{
"foo": [1, 2, 3]
}
[
{"errorCode": 10001},
{"errorCode": 10002}
]
// It's a constant
// Also, ResponseWrap<Map<String,List<Integer>>>.class is illegal in Java
private static final Type type = new TypeToken<ResponseWrap<Map<String, List<Integer>>>>() {
}.getType();
public static void main(String... args)
throws IOException {
final String successJson = getPackageResourceString(Q43525433.class, "success.json");
final String failureJson = getPackageResourceString(Q43525433.class, "failure.json");
final ResponseWrap<Map<String, List<Integer>>> success = gson.fromJson(successJson, type);
final ResponseWrap<Map<String, List<Integer>>> failure = gson.fromJson(failureJson, type);
System.out.println("SUCCESS: " + success.response);
for ( final ErrorResponse response : failure.errorResponses ) {
System.out.println("FAILURE: " + response.errorCode);
}
}
输出:
成功:{foo = [1,2,3]}
失败:10001
失败:10002
是的,不要忘记使用gson
将GsonConverterFactory.create(gson)
添加到Retrofit。
此外,您可能对Json response parser for Array or Object感兴趣,但从另一个角度描述了几乎相同的问题。