ClassCastException 对象继承与改造
ClassCastException Object Inheritance with Retrofit
我正在为我们的核心网络从 Asynctask + Loader 迁移到 Retrofit。一切顺利,直到我遇到了以前没有发生过的 ClassCastExceptions:
ImagePojo imagePojo = (ImagePojo) mediaPojo; // Error: cannot cast MediaPojo to ImagePojo
这是层次结构中我的 POJO 的骨架:
Parent/Generic 媒体对象
public class MediaPojo extends CustomObservable implements Parcelable {
@SerializedName("media_id")
private String mMid;
@SerializedName("type")
private String mType;
@SerializedName("title")
public MediaPojo() {
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mNid);
dest.writeString(mType);
dest.writeString(mTitle);
}
public static final Parcelable.Creator<MediaPojo> CREATOR = new Parcelable.Creator<MediaPojo>() {
@Override
public MediaPojo createFromParcel(Parcel source) {
return new MediaPojo(source);
}
@Override
public MediaPojo[] newArray(int size) {
return new MediaPojo[size];
}
};
...
}
// 定义所有媒体类型通用的字段
视频媒体类型
public class VideoPojo extends MediaPojo implements Parcelable {
@SerializedName("video_id")
private String mVid;
@SerializedName("video_url")
private String mVideoUrl;
@SerializedName("thumbnail")
private String mThumbnail ...
}
图像媒体类型 POJO
public class ImagePojo extends MediaPojo implements Parcelable {
@SerializedName("image_url")
private String mImageUrl;
@SerializedName("width")
private String mImageWidth;
@SerializedName("height")
private String mImageHeight
...
}
// 其他类型:TextPojo、InfoGraphicPojo ...
到目前为止,我在迁移中对 POJO 所做的唯一更改是添加 @SerializedName 注释以允许 Retrofit 自动将 JSON 字段映射到定义的 POJO 实例变量。我发现有必要在运行时将通用 MediaPojo 转换为特定类型的 POJO,因为我对 api.mydomain.com/media 的 REST 调用可以 return JSON 表示各种类型的媒体(视频、图片等)。
有没有办法让 Retrofit 绕过我已经存在的对象继承结构?或者它可以像我使用 Parcelable 接口进行序列化的方式那样吗?
是的,问题是 gson 总是在改造调用中创建类型。您需要提供一个 custom deserializer 来根据您的 type
字段找出对象类型。这是可能看起来像的草图。
public class ModiaPojoDeserializer implements JsonDeserializer<Model> {
private static final String TYPE_FIELD = "type";
private static final String IMAGE_TYPE = "image";
private static final String VIDEO_TYPE = "video";
@Override
public Model deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonObject() && json.getAsJsonObject().has(TYPE_FIELD)) {
JsonObject jsonObject = json.getAsJsonObject();
final String type = jsonObject.get(TYPE_FIELD).getAsString();
if (VIDEO_TYPE.equals(type)) {
return context.deserialize(json, VideoPojo.class);
} else if (IMAGE_TYPE.equals(type)) {
return context.deserialize(json, ImagePojo.class);
} // ...
// If you need to deserialize as MediaPojo,
// deserialize without the current context
// or you will infinite loop
return new Gson().fromJson(json, typeOfT);
} else {
// Return a blank object on error, could use null
return new MediaPojo();
}
}
}
然后您将使用 Gson.Builder
使用您的自定义反序列化器创建一个自定义 gson 对象 -
Gson gson = new GsonBuilder()
.registerTypeAdapter(MediaPojo.class, new MediaPojoDeserializer())
.create();
然后在创建 RestAdapter
时添加它 --
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.mydomain.com")
.setConverter(new GsonConverter(gson))
.build();
我正在为我们的核心网络从 Asynctask + Loader 迁移到 Retrofit。一切顺利,直到我遇到了以前没有发生过的 ClassCastExceptions:
ImagePojo imagePojo = (ImagePojo) mediaPojo; // Error: cannot cast MediaPojo to ImagePojo
这是层次结构中我的 POJO 的骨架:
Parent/Generic 媒体对象
public class MediaPojo extends CustomObservable implements Parcelable {
@SerializedName("media_id")
private String mMid;
@SerializedName("type")
private String mType;
@SerializedName("title")
public MediaPojo() {
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mNid);
dest.writeString(mType);
dest.writeString(mTitle);
}
public static final Parcelable.Creator<MediaPojo> CREATOR = new Parcelable.Creator<MediaPojo>() {
@Override
public MediaPojo createFromParcel(Parcel source) {
return new MediaPojo(source);
}
@Override
public MediaPojo[] newArray(int size) {
return new MediaPojo[size];
}
};
...
}
// 定义所有媒体类型通用的字段
视频媒体类型
public class VideoPojo extends MediaPojo implements Parcelable {
@SerializedName("video_id")
private String mVid;
@SerializedName("video_url")
private String mVideoUrl;
@SerializedName("thumbnail")
private String mThumbnail ...
}
图像媒体类型 POJO
public class ImagePojo extends MediaPojo implements Parcelable {
@SerializedName("image_url")
private String mImageUrl;
@SerializedName("width")
private String mImageWidth;
@SerializedName("height")
private String mImageHeight
...
}
// 其他类型:TextPojo、InfoGraphicPojo ...
到目前为止,我在迁移中对 POJO 所做的唯一更改是添加 @SerializedName 注释以允许 Retrofit 自动将 JSON 字段映射到定义的 POJO 实例变量。我发现有必要在运行时将通用 MediaPojo 转换为特定类型的 POJO,因为我对 api.mydomain.com/media 的 REST 调用可以 return JSON 表示各种类型的媒体(视频、图片等)。
有没有办法让 Retrofit 绕过我已经存在的对象继承结构?或者它可以像我使用 Parcelable 接口进行序列化的方式那样吗?
是的,问题是 gson 总是在改造调用中创建类型。您需要提供一个 custom deserializer 来根据您的 type
字段找出对象类型。这是可能看起来像的草图。
public class ModiaPojoDeserializer implements JsonDeserializer<Model> {
private static final String TYPE_FIELD = "type";
private static final String IMAGE_TYPE = "image";
private static final String VIDEO_TYPE = "video";
@Override
public Model deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonObject() && json.getAsJsonObject().has(TYPE_FIELD)) {
JsonObject jsonObject = json.getAsJsonObject();
final String type = jsonObject.get(TYPE_FIELD).getAsString();
if (VIDEO_TYPE.equals(type)) {
return context.deserialize(json, VideoPojo.class);
} else if (IMAGE_TYPE.equals(type)) {
return context.deserialize(json, ImagePojo.class);
} // ...
// If you need to deserialize as MediaPojo,
// deserialize without the current context
// or you will infinite loop
return new Gson().fromJson(json, typeOfT);
} else {
// Return a blank object on error, could use null
return new MediaPojo();
}
}
}
然后您将使用 Gson.Builder
使用您的自定义反序列化器创建一个自定义 gson 对象 -
Gson gson = new GsonBuilder()
.registerTypeAdapter(MediaPojo.class, new MediaPojoDeserializer())
.create();
然后在创建 RestAdapter
时添加它 --
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.mydomain.com")
.setConverter(new GsonConverter(gson))
.build();