【问题标题】:Converting Error Response Array with Retrofit2 converter使用 Retrofit2 转换器转换错误响应数组
【发布时间】:2017-09-17 10:28:36
【问题描述】:

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

[
 {
  "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 个错误:

  • 列表错误 = new Gson().fromJson(jsonObject.getAsJsonObject("error"), ArrayList.class); // 不能从参数化类中选择

  • T 成功 = 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 以通用方式处理数组中的错误响应?

【问题讨论】:

    标签: java android gson retrofit2


    【解决方案1】:

    你不能写T.class——这在Java中是非法的。为了克服这个限制,您必须以某种方式自己传递Type 实例,或者从 Gson 给您的解析泛型类型参数。在第一种情况下,您需要十几个 JSON 反序列化器来绑定各种 ResponseWrap&lt;T&gt; 参数化;而在第二种情况下,可以简单地自己解决实际的类型参数。在调用站点,您可以使用TypeTokens——一种特殊的 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();
    

    并用它测试它

    成功.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

    是的,别忘了使用GsonConverterFactory.create(gson)gson 添加到Retrofit。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-07
      • 1970-01-01
      • 2013-11-15
      • 1970-01-01
      • 2016-12-20
      相关资源
      最近更新 更多