使用 GSON 调整 Retrofit 响应

Adapting Retrofit responses using GSON

我想不可知论地检索已知 JSON 对象的子元素以及我从特定 API.

收到的每个成功响应

每个服务器响应 return 都采用以下 JSON 格式(为简单起见进行了压缩):

{
    "status": "success",
    "error_title": "",
    "error_message": "",
    "data": {
        "messages": [
            { "message_id": "123",
              "content": "This is a message" },
            { "message_id": "124",
              "content": "This is another message" }
        ]
    }
}

错误响应包含相同的通用格式,"data" 对象为空,错误相关的 JSON 对象包含有用的值。在出现错误的情况下,我想提取与错误相关的 JSON 个对象。

根据上述响应,我有一个 MessageResponse class,其中包含 status、errorTitle 和 errorMessage 字符串属性以及一个 MessageData 对象。 MessageData 对象然后包含消息列表 - List<Message> messages。我在这种情况下获取消息的 GET 方法如下(为简单起见进行了压缩):

@GET("/chat/conversation")
void getMessages(Callback<MessageResponse> callback);

如果我坚持使用 GSON 的序列化器提供的开箱即用的默认 POJO 映射,那么此设计需要三个 classes 用于 each 响应类型。我的最终目标是通过仅从成功的服务器响应中读取我需要的内容而忽略其余部分来减少必要的 classes 数量。我希望此 API 上的所有成功回调数据类型尽可能接近 "data" 内容。

换句话说,我想不可知return "data" 的子元素。在上面的例子中,它是一个名为 "messages" 的数组,但在某些其他响应中它可能是一个 "user" 对象。我知道这可以通过为每种响应类型注册单独的 TypeAdapters 来完成,但我想通过使用单个 generic 解决方案来实现我的最终目标。

更新:执行下面大卫的建议

public class BaseResponse<T> {
     @SerializedName("status") public String status;
     @SerializedName("error_title") public String errorMessageTitle;
     @SerializedName("error_message") public String errorMessage;
     @SerializedName("data") public T data;
}

public class MessagesResponse extends BaseResponseData<List<Message>> {
     @SerializedName("messages") List<Message> messages;
}

@GET("/chat/conversation")
void getMessages(Callback<BaseResponse<MessageResponse>> callback);

不幸的是,这没有得到正确的序列化。如果我能以某种方式通知 GSON 来自 "data" 父对象的可变名称 JSON 对象子对象,并将该子对象反序列化为通用数据类型引用的模型 class。本质上,dataJsonObject.getChild().

这并不是您问题的真正答案,但可能是对具有多个冗余 class 的相同问题的替代解决方案,用于许多类似的响应:

这是我们的 AbstractResponse:

public abstract class AbstractResponse<T> {

    @SerializedName("success")
    private boolean success;

    // used for error handling
    @SerializedName("error")
    private String errorMessage;

    @SerializedName("code")
    private Integer errorCode;

    // used for normal operation
    @SerializedName("data")
    protected T data;

    @SerializedName("details")
    private DetailsError details;

    @SerializedName("points")
    private Integer points;

    public boolean isSuccess() {
        return success;
    }

    public T getData() {
        return data;
    }

    public DetailsError getDetailsError() {
        return details;
    }

    public Integer getPoints() {
        return points;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public Integer getErrorCode() {
        return errorCode;
    }

    public AbstractResponse(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "AbstractResponse{" +
                "success=" + success +
                ", errorMessage='" + errorMessage + '\'' +
                ", errorCode=" + errorCode +
                ", data=" + data +
                '}';
    }
}

然后有 class 个像这样的:

public class VotingImageListResponse extends AbstractResponse<List<VotingImage>> {

    public VotingImageListResponse(List<VotingImage> data) {
        super(data);
    }

}

Retrofit 像这样使用它们:

@GET("/api/VotingImage")
public void getVotingImages(@Query("voting_id") Integer id, @Query("app_user_id") Integer userId, @Query("session") String sessionId, Callback<VotingImageListResponse> callback);

仅此而已。

编辑:

为了更清楚,这是VotingImage:

public class VotingImage implements Parcelable {

    @SerializedName("voting_image_id")
    private final Integer votingImageId;

    @SerializedName("voting_id")
    private final Integer votingId;

    @SerializedName("image_id")
    private final Integer imageId;

    @SerializedName("url")
    private final Uri uri;

    @SerializedName("url_small")
    private final Uri uriSmall;

    // ...
}

更多具体响应示例 classes:

public class ChoiceResponse extends AbstractResponse<Choice> {
    public ChoiceResponse(Choice data) {
        super(data);
    }
}

其中Choice定义如下:

public class Choice {

    @SerializedName("question_list")
    private final PVector<Question> questions;

    @SerializedName("is_evaluation")
    private final Boolean isEvaluation;

   // ...
}

或:

public class RegisterResponse extends AbstractResponse<RegisterResponseData>{
    public RegisterResponse(RegisterResponseData data) {
        super(data);
    }
}

与:

public class RegisterResponseData {

    @SerializedName("mail")
    private String email;

    @SerializedName("app_user_id")
    private Integer appUserId;

    @SerializedName("name")
    private String name;

    @SerializedName("session")
    private String sessionId;

    // ...
}

如您所见,即使 JSON 属性始终称为 "data",但每个响应的该字段的 type/content 可能会有很大差异。唯一重要的是 Retrofit 知道(以便它可以告诉 Gson)您预期响应的类型。上面的通用 class 结构只是 - 我认为 - 告诉 Retrofit/Gson 将 JSON 解析为什么的简洁方式。上面的例子方法也可以直接这样写:

@GET("/api/VotingImage")
public void getVotingImages(@Query("voting_id") Integer id, @Query("app_user_id") Integer userId, @Query("session") String sessionId, Callback<AbstractResponse<List<VotingImage> callback);

还有一点:这个没有测试过,我现在真的不能测试,但是这个怎么样:

public abstract class MyAbstractCallback<T> implements Callback<AbstractResponse<T>> {

    @Callback
    public void onSuccess(AbstractResponse<T> response) {
        // if (response was successful) {
            T data = response.getData();
            onRealSuccess(data);
        // }
    }

    public abstract void onRealSuccess(T data);
}

通过这种方式,您还可以从整个通用响应中提取出 "unpacking" 实际响应数据。

在向 GSON 提供通用的基本响应 类 失败几个小时后,我最终通过了那条路线并确定了我几天前实施的解决方案(减去状态检查条件) .

GSON 通过在通用 TypeAdapterFactory 中定义反序列化逻辑,提供了向所有响应添加 TypeAdapter 的能力。这个实体并不像我希望的那样干净和无知,但它在减少必要响应模型的数量方面发挥了作用类,同时还维护了一个适配器。

private static class ResponseTypeAdapterFactory implements TypeAdapterFactory {

    private static final String STATUS = "status";
    private static final String SUCCESS = "success";
    private static final String DATA = "data";

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, type);
        final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                delegateAdapter.write(out, value);
            }

            @Override
            public T read(JsonReader in) throws IOException {
                // Ignore extraneous data and read in only the response data when the response is a success
                JsonElement jsonElement = jsonElementAdapter.read(in);
                if (jsonElement.isJsonObject()) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();
                    if (jsonObject.has(STATUS)) {
                        if (jsonObject.get(STATUS).getAsString().equals(SUCCESS)) {
                            if (jsonObject.has(DATA) && jsonObject.get(DATA).isJsonObject()) {
                                jsonElement = jsonObject.get(DATA);
                            }
                        }
                    }
                }
                return delegateAdapter.fromJsonTree(jsonElement);
            }
        }.nullSafe();
    }
}

简而言之,如果响应成功,我告诉 GSON 获取 "data" JSON 对象。否则,return 整个响应主体,以便我的自定义 Retrofit 错误处理程序可以使用从服务器 return 编辑的 "error_title" 和 "error_message" 字段。

非常感谢@david.mihola 提出了很好的建议,并最终将我的注意力引回了 TypeAdapterFactory 解决方案。