【问题标题】:Retrofit Gson custom json generic converter改造 Gson 自定义 json 通用转换器
【发布时间】:2019-09-26 14:10:04
【问题描述】:

我之前在我的几个项目中与 Retrofit 合作过,但现在我想做一些稍微不同的事情。我正在调用一个 api,它将我的响应包装在一个类似于这样的结构中:

{ // only for demo purposes. Probably errors and data will never be populated together
  "body": { 
    "errors": {
      "username": [
        "Username is too short",
        "Username already exists"
      ]
    },
    "data": {
      "message": "User created."
    }
  }
}

我正在尝试将所有这些转换为一个通用类,该类将为我包装该响应。我的想法是这样的

public class ApiResponse<T> {
    private T data;

    private Map<String, List<String>> errors;

    public ApiResponse(T data, Map<String, List<String>> errors) {
        this.data = data;
        this.errors = errors;
    }
}

T 可以是任何类。

我尝试根据我在互联网上找到的一些示例来实现JsonDeserializer&lt;ApiResponse&lt;T&gt;&gt;,但我无法理解如何使其尽可能自动地工作并让 Retrofit 和 Gson 完成繁重的工作 我的转换器类如下:

public class ApiResponseDeserializer<T> implements JsonDeserializer<ApiResponse<T>> {

    private Class clazz;

    public ApiResponseDeserializer(Class clazz) {
        this.clazz = clazz;
    }

    @Override
    public ApiResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        final JsonObject jsonObject = json.getAsJsonObject();

        final JsonObject body = jsonObject.getAsJsonObject("body");

        final JsonObject errors = body.getAsJsonObject("errors");
        final JsonObject data = body.getAsJsonObject("data");

        Map<String, List<String>> parsedErrors = new HashMap<>();
        for(String key : errors.keySet()) {
            List<String> errorsList = new ArrayList<>();

            JsonArray value = errors.getAsJsonArray(key);
            Iterator<JsonElement> valuesIterator = value.iterator();
            while(valuesIterator.hasNext()) {
                String error = valuesIterator.next().getAsString();

                errorsList.add(error);
            }

            parsedErrors.put(key, errorsList);
        }

        T parsedData = context.deserialize(data, clazz);

        return new ApiResponse<T>(parsedData, parsedErrors);
    }
}

然后在构建我的改造客户端时

public static Retrofit getClient() {

        if (okHttpClient == null) {
            initOkHttp();
        }

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(ApiResponse.class, new ApiResponseDeserializer<>(......) // PROBLEM
                .create();

        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(Const.API_BASE_URL)
                    .client(okHttpClient)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();
        }

        return retrofit;
    }

但我觉得它不够通用,无法自动转换我的类。而且我也不知道应该如何提示Gson 我的data 是什么类型。

我的端点定义如下:

@POST("users/signup")
Single<ApiResponse<RegisterResponseData>> register(@Body RegisterRequest request);

但是如何使用通用 Gson 类型适配器创建通用 Retrofit 实例,该适配器知道如何将我的响应转换为 ApiResponse&lt;RegisterResponseData&gt;?并且知道响应中的data 属性应该转换为RegisterResponseData 类型的对象...

【问题讨论】:

    标签: java android json gson retrofit2


    【解决方案1】:

    当您在 Retrofit 的客户端中指定返回类型时,它会作为 Type 传递给 Retrofit 的转换器,然后 Gson 会接收到您的 ApiResponse&lt;RegisterResponseData&gt; 类型。从那时起,Gson 将了解 data 的类型为 RegisterResponseData 并将生成您的模型对象。

    在没有ApiResponseDeserializer 的情况下尝试一下,您会发现它正在工作。

    编辑: 在 cmets 中回答您的其他问题:

    如果你想在 json 中跳过你的“body”对象,你可以这样写你的包装对象:

    public class ApiResponse<T> {
    
       @SerializedName("body")
       private ApiResponseBody<T> body;
    
       public ApiResponse() {
       }
    
       public ApiResponse(ApiData<T> body) { 
           this.body = body;
       }
    }
    
    public class ApiResponseBody<T> {
    
       @SerializedName("data")
       private T data;
    
       @SerializedName("errors")
       private Map<String, List<String>> errors;
    
       public ApiResponseBody() {
       }
    
       public ApiResponseBody(T data, Map<String, List<String>> errors) {
           this.data = data;
           this.errors = errors;
       }
    }
    

    并以通常的方式使用它

    @POST("users/signup")
    Single<ApiResponse<RegisterResponseData>> register(@Body RegisterRequest request);
    

    【讨论】:

    • 哦,实际上是的.. 有趣.. 我以为我必须手动提供我的通用类进行转换,但似乎我没有.. 知道如何跳过里面有一个 Body 对象ApiResponse?我可能只是继续使用我的ApiResponseDeserializer 只是为了到达json 中的“body”标签所指的对象,然后尝试context.deserialize 那..
    • 要跳过正文,您可以编写一个包装器对象。我会在回答中解释
    • 我之前也一直在使用这种“包装对象”技术,但我正在考虑完全跳过它,这样我就不必再调用 ApiResponsegetBody() 方法实际获取我的数据和错误.. 我将使用我的ApiResponseDeserializer 执行final JsonObject body = jsonObject.getAsJsonObject("body"); 然后return context.deserialize(body, typeOfT) 将我的错误和数据直接放入ApiResponse 中,没有包装对象。再次非常感谢!它比我想象的要简单得多,但无法真正找到任何地方记录的通用内容
    • 所以我不必额外调用 ApiResponse 的 getBody() 您可以编写方法来省略对 getBody() 的调用 public T getData() { return getBody().getData() }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-11
    • 1970-01-01
    • 2015-05-15
    相关资源
    最近更新 更多