简体   繁体   English

Retrofit Gson 定制 json 通用转换器

[英]Retrofit Gson custom json generic converter

I've been working with Retrofit on a couple of my projects before but now I want to do something slightly different.我之前在我的几个项目中一直在使用 Retrofit,但现在我想做一些稍微不同的事情。 I'm calling an api that wraps my response in a structure similar to this:我正在调用一个 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."
    }
  }
}

I'm trying to convert all that to a generic class which will wrap that response for me.我正在尝试将所有这些转换为通用的 class ,它将为我包装该响应。 What I have in mind is something like我的想法是

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;
    }
}

Where T can be any class.其中T可以是任何 class。

I tried implementing a JsonDeserializer<ApiResponse<T>> based on some examples I found around the internet but I can't wrap my head around how to make it work as much automatically as possible and let Retrofit and Gson do the heavy lifting My Converter class is as follows:我尝试根据我在互联网上找到的一些示例来实现JsonDeserializer<ApiResponse<T>>但我无法理解如何使其尽可能自动地工作并让 Retrofit 和 Gson 完成繁重的工作class如下:

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);
    }
}

and then when building my retrofit client然后在构建我的 retrofit 客户端时

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;
    }

But I feel like it's not generic enough to be able to convert my classes automatically.但我觉得它不够通用,无法自动转换我的类。 And also I have no idea how should I hint Gson what type my data .而且我不知道我应该如何提示Gson我的data类型。

My endpoints are defined as follows:我的端点定义如下:

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

But how do I make a generic Retrofit instance with a generic Gson type adapter that knows how to convert my response to a ApiResponse<RegisterResponseData> ?但是,如何使用知道如何将我的响应转换为ApiResponse<RegisterResponseData>的通用 Gson 类型适配器制作通用 Retrofit 实例? And knows that the data property from the response should be converted to an object of type RegisterResponseData ...并且知道响应中的data属性应该转换为RegisterResponseData类型的 object ...

When you specify return type in Retrofit's client it's passed to Retrofit's converter as Type and then Gson receives that type which will be your ApiResponse<RegisterResponseData> .当您在 Retrofit 的客户端中指定返回类型时,它将作为 Type 传递给 Retrofit 的转换器,然后 Gson 接收该类型,这将是您的ApiResponse<RegisterResponseData> From that point Gson will understand that data is of type RegisterResponseData and will produce your model object.从那时起,Gson 将了解dataRegisterResponseData类型,并将生成您的 model object。

Just try it without your ApiResponseDeserializer and you'll see it's working.只需在没有ApiResponseDeserializer的情况下尝试它,您就会看到它正在工作。

Edit: Answering your additional question in comments:编辑:在评论中回答您的其他问题:

If you want to skip your "body" object in json you can write your wrapper object like this:如果你想在 json 中跳过你的“body”object,你可以像这样编写你的包装器 object:

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;
   }
}

And use it in usual way并以通常的方式使用它

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM