我希望通过从特定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字符串属性以及MessageData
对象。然后MessageData
对象包含消息列表 - List<Message> messages
。在这种情况下获取消息的我的GET方法如下(为简单起见而压缩):
@GET("/chat/conversation")
void getMessages(Callback<MessageResponse> callback);
如果我坚持使用GSON的序列化程序提供的默认POJO映射,那么这个设计需要三个每个响应类型的类。我的最终目标是通过仅从成功的服务器响应中读取我需要的内容并忽略其余类来减少所需的类数量。我希望我所有的成功,这个API上的回调数据类型尽可能接近“数据”内容。
换句话说,我想非常地返回“数据”的子元素。在上面的例子中,它是一个名为“messages”的数组,但在某些其他响应中,它可能是一个“用户”对象,例如。我知道这可以通过为每种响应类型注册单独的TypeAdapter
来完成,但我希望通过使用单个泛型解决方案来实现我的最终目标。
更新:从下面实施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()
。
答案 0 :(得分:9)
在向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
解决方案。
答案 1 :(得分:1)
对于你的问题,这不是一个真正的答案,但也许是针对许多类似响应的多个冗余类的相同问题的替代解决方案:
这是我们的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);
}
通过这种方式,您还可以从整个通用响应中分解出实际响应数据的“解包”。