问题 使用GSON调整改造响应


我希望通过从特定API收到的每个成功响应,以非常方式检索已知JSON对象的子元素。

每个服务器响应都返回以下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 包含status,errorTitle和errorMessage字符串属性的类以及a MessageData 目的。该 MessageData 对象然后包含一个消息列表 - List<Message> messages。在这种情况下获取消息的我的GET方法如下(为简单起见,简化):

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

这种设计需要三个类  响应类型,如果我坚持GSON的序列化程序提供的默认POJO映射开箱即用。我的最终目标是通过仅从成功的服务器响应中读取我需要的内容并忽略其余类来减少所需的类的数量。我希望我所有的成功,这个API上的回调数据类型尽可能接近“数据”内容。

换句话说,我想非常地返回“数据”的子元素。在上面的例子中,它是一个名为“messages”的数组,但在某些其他响应中,它可能是一个“用户”对象,例如。我知道这可以通过单独注册来完成 TypeAdapter每个响应类型的s,但我想通过使用单个,达到我的最终目标, 通用 解。

更新:从下面实施David的建议

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对象子级,并将该子级反序列化为通用数据类型引用的模型类。从本质上讲, dataJsonObject.getChild()


10448
2018-05-08 02:22


起源

所以你有一个固定的JSON结构,但是 data 可能 什么?或者只是众多预定义类型中的任何一种?如果后者是真的,我已经使用了泛型 BaseResponse<T> 哪里 data 是类型 T。然后,例如,a UserResponse extends BaseResponse<User> 只需要实现一个构造函数并从中继承其他所有内容 BaseResponse。 - david.mihola
@ david.mihola谢谢你的好主意。我尝试将我的模型设置适合您提出的设计,但我收到了一个空列表的帖子。请参阅上面的我的修改。您能提供示例代码来说明您的解决方案吗? - Ryan
我刚从我们的项目中发布了示例类! - david.mihola


答案:


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

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

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抓取“数据”JSON对象。否则,返回整个响应主体,以便我的自定义Retrofit错误处理程序可以使用从服务器返回的“error_title”和“error_message”字段。

对@ david.mihola的巨大呐喊,提出了很好的建议,并最终将我的注意力转回到了 TypeAdapterFactory 解。


9
2018-05-10 22:37



在改造1.9中,这就像一个魅力,但在改造2.0中,我无法实现这一点。有关如何使这项工作适用于改造2.0的任何想法?谢谢\ - Mudit Jaju


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

这是我们的 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 +
                '}';
    }
}

然后有像这样的类:

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;

    // ...
}

更多具体响应类的示例:

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属性始终称为“数据”,该字段的类型/内容也可能因每个响应而有很大差异。唯一重要的事情是Retrofit知道(这样它可以告诉Gson)您预期的响应类型。上面的泛型类结构只是 - 我认为 - 简明的方式告诉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);
}

通过这种方式,您还可以将整个通用响应中的实际响应数据的“解包”分解出来。


1
2018-05-10 08:18



我认为这与我正在寻找的很接近,但不幸的是,API返回一个嵌套的,可变命名的JSON对象 中 “数据”,我认为我无法满足您的解决方案。 “数据”键总是不变的,但“数据”的子元素总是可变的(例如上面的情况中的“消息”或“用户”,“说话者”,无论它是什么)。我似乎无法找到一种方法来提取该变量,而无需在@SerializedName(key)注释中明确命名其键。 - Ryan
一个问题:服务器URL / Retrofit方法与响应类型之间是否存在直接对应关系,即。即对于每个单个请求,您是否事先知道JSON中使用的名称和类型? - david.mihola
HTTP URL与数据的子类型之间没有关系。我知道类型的唯一方法是查找API文档以检查密钥。我试图想出一种方法来传递一个字符串,该字符串表示基于给定数据的子项密钥到GSON反序列化器 Callback<T> 类型。 - Ryan
我并不一定意味着“明显的”或“不言而喻”的关系。即URL“/ myapi / <x> /”将始终在JSON中返回“X”。但是:当你调用API时,你知道会发生什么吗? - david.mihola
是的,我确实知道任何API响应会有什么期望。它有点暗示 Callback 类型。 - Ryan