使用Retrofit2转换器转换错误响应数组

时间:2017-04-20 16:49:56

标签: java android gson retrofit2

每当我收到错误时,错误正文如下:

[
 {
  "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以通用方式处理数组中的错误响应?

1 个答案:

答案 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();

进行测试

success.json

{
    "foo": [1, 2, 3]
}

failure.json

[
    {"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

是的,不要忘记使用gsonGsonConverterFactory.create(gson)添加到Retrofit。

此外,您可能对Json response parser for Array or Object感兴趣,但从另一个角度描述了几乎相同的问题。