Retrofit、Gson 和一组异构对象
Retrofit, Gson and an array of heterogeneous objects
我正在使用 Retrofit
向我们的服务器执行 REST 请求。这些请求之一 return 一个 array
的对象,一旦在 POJO
中反序列化,就会从 abstract class Event
扩展。事件的方法 getEventType()
return 是 String
,这个字符串是键 "EventType"
的值,我将始终在 JSONObject
中大批。
这就是 JSON
的样子(我们现在有 7 种类型的对象):
[
{
"EventType":"typeA",
"Data":"data"
},
{
"EventType":"typeB",
"OtherData":3
}
]
我正在尝试使用 Retrofit
和 GSON
API
s 在异步调用中反序列化这个 JSON
,使用 Callback<List<Event>>
作为调用的参数,但我仍然找不到方法。
我不确定,因为我还没有测试过这个,但是如果你像这样写一个自定义反序列化器:
private class MyEventDeserialiser implements JsonDeserializer<Event> {
@Override
public Event deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonArray jArray = (JsonArray) json;
for (int i=1; i<jArray.size(); i++) {
JsonObject obj = jArray.get(i).getAsJsonObject();
String eventType = String.valueOf(obj.get("EventType"));
//check here which type it is
Event event = null;
if(eventType.equals("TypeA")) {
Event event = context.deserialize(obj, TypeA.class);
}
...
return event;
}
}
}
然后在用于 Retrofit 的 Gson 解串器上设置这个,它可能会起作用。
您可能必须将事件列表封装在另一个 class 中,例如
public class EventResponse {
List<Event> events;
}
然后在您的界面中将其用作参数,但我不确定。
您可以为这种情况编写自定义 Gson TypeAdapterFactory
。事情是确定事件的类型,然后为该类型使用默认值 TypeAdapter
。
这正是我所做的:
public class EventTypeAdapterFactory implements TypeAdapterFactory {
private static final String TAG = EventTypeAdapterFactory.class.getSimpleName();
private Map<EventType, TypeAdapter<? extends Event>> ADAPTERS = new ArrayMap<>();
private TypeAdapter<Event> baseTypeAdapter;
private TypeAdapter<JsonElement> elementAdapter;
private TypeAdapter<EventType> eventTypeAdapter;
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!Event.class.isAssignableFrom(type.getRawType())) return null;
ADAPTERS.put(EventType.TYPE_A, gson.getDelegateAdapter(this, TypeToken.get(TypeAEvent.class)));
ADAPTERS.put(EventType.TYPE_B, gson.getDelegateAdapter(this, TypeToken.get(TypeBEvent.class)));
baseTypeAdapter = gson.getDelegateAdapter(this, TypeToken.get(Event.class));
elementAdapter = gson.getAdapter(JsonElement.class);
eventTypeAdapter = gson.getAdapter(EventType.class);
return (TypeAdapter<T>) new EventTypeAdapter().nullSafe();
}
private class EventTypeAdapter extends TypeAdapter<Event> {
@Override public void write(JsonWriter out, Event value) throws IOException {
EventType eventType = value.getType();
TypeAdapter<? extends Event> adapter = eventType == null ? baseTypeAdapter : ADAPTERS.get(eventType);
if (value instanceof TypeAEvent) {
writeWrap(adapter, out, (TypeAEvent) value, TypeAEvent.class);
} else if (value instanceof TypeBEvent) {
writeWrap(adapter, out, (TypeBEvent) value, TypeBEvent.class);
} else {
writeWrap(adapter, out, value, Event.class);
}
}
private <T extends Event> void writeWrap(TypeAdapter<? extends Event> adapter,
JsonWriter out, T value, Class<T> dummyForT) throws IOException {
((TypeAdapter<T>)adapter).write(out, value);
}
@Override public Event read(JsonReader in) throws IOException {
JsonObject objectJson = elementAdapter.read(in).getAsJsonObject();
JsonElement typeJson = objectJson.get("EventType");
EventType eventType = eventTypeAdapter.fromJsonTree(typeJson);
if (eventType == null) {
Log.w(TAG, "Unsupported EventType: " + typeJson);
}
TypeAdapter<? extends Event> adapter = eventType == null ? baseTypeAdapter : ADAPTERS.get(eventType);
return adapter.fromJsonTree(objectJson);
}
}
}
// EventType enum, change to reflect your values.
enum EventType {
TYPE_A, TYPE_B;
}
// Base Event type and its successors.
class Event {
@SerializedName("EventType")
private EventType type;
public EventType getType() {
return type;
}
}
class TypeAEvent extends Event {
@SerializedName("Data")
public String data;
}
class TypeBEvent extends Event {
@SerializedName("OtherData")
public int otherData;
}
与 colriot 的回答非常相似。我做了一些修改,以便我将 class 嵌入到 json 中,并且仅当它被序列化时 - 该地图有点难看。
它也不那么健壮,因为我很高兴在 null 上失败。
public final class ModelTypeAdapter extends TypeAdapter<Model> {
private static final String MODEL_CLASS_PROPERTY_NAME = "modelClass";
private final Gson gson;
private final TypeAdapterFactory containerFactory;
@SuppressWarnings("rawtypes")
private final TypeAdapter<Class> classTypeAdapter;
private final TypeAdapter<JsonElement> jsonElementAdapter;
public ModelTypeAdapter(final Gson gson, final TypeAdapterFactory containerFactory) {
this.gson = gson;
this.containerFactory = containerFactory;
this.classTypeAdapter = gson.getAdapter(Class.class);
this.jsonElementAdapter = gson.getAdapter(JsonElement.class);
}
@Override
public final void write(JsonWriter out, Model value) throws IOException {
doWrite(out, value);
}
private final <M extends Model> void doWrite(JsonWriter out, M value) throws IOException {
@SuppressWarnings("unchecked")
final Class<M> modelClass = (Class<M>) value.getClass();
final TypeAdapter<M> delegateAdapter = gson.getDelegateAdapter(containerFactory, TypeToken.get(modelClass));
final JsonObject jsonObject = delegateAdapter.toJsonTree(value).getAsJsonObject();
jsonObject.add(MODEL_CLASS_PROPERTY_NAME, classTypeAdapter.toJsonTree(modelClass));
jsonElementAdapter.write(out, jsonObject);
}
@Override
public final Model read(JsonReader in) throws IOException {
final JsonObject jsonObject = jsonElementAdapter.read(in).getAsJsonObject();
@SuppressWarnings("unchecked")
final Class<? extends Model> modelClass = classTypeAdapter.fromJsonTree(jsonObject.get(MODEL_CLASS_PROPERTY_NAME));
jsonObject.remove(MODEL_CLASS_PROPERTY_NAME);
final TypeAdapter<? extends Model> delegateAdapter = gson.getDelegateAdapter(containerFactory, TypeToken.get(modelClass));
return delegateAdapter.fromJsonTree(jsonObject);
}
}
我正在使用 Retrofit
向我们的服务器执行 REST 请求。这些请求之一 return 一个 array
的对象,一旦在 POJO
中反序列化,就会从 abstract class Event
扩展。事件的方法 getEventType()
return 是 String
,这个字符串是键 "EventType"
的值,我将始终在 JSONObject
中大批。
这就是 JSON
的样子(我们现在有 7 种类型的对象):
[
{
"EventType":"typeA",
"Data":"data"
},
{
"EventType":"typeB",
"OtherData":3
}
]
我正在尝试使用 Retrofit
和 GSON
API
s 在异步调用中反序列化这个 JSON
,使用 Callback<List<Event>>
作为调用的参数,但我仍然找不到方法。
我不确定,因为我还没有测试过这个,但是如果你像这样写一个自定义反序列化器:
private class MyEventDeserialiser implements JsonDeserializer<Event> {
@Override
public Event deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonArray jArray = (JsonArray) json;
for (int i=1; i<jArray.size(); i++) {
JsonObject obj = jArray.get(i).getAsJsonObject();
String eventType = String.valueOf(obj.get("EventType"));
//check here which type it is
Event event = null;
if(eventType.equals("TypeA")) {
Event event = context.deserialize(obj, TypeA.class);
}
...
return event;
}
}
}
然后在用于 Retrofit 的 Gson 解串器上设置这个,它可能会起作用。
您可能必须将事件列表封装在另一个 class 中,例如
public class EventResponse {
List<Event> events;
}
然后在您的界面中将其用作参数,但我不确定。
您可以为这种情况编写自定义 Gson TypeAdapterFactory
。事情是确定事件的类型,然后为该类型使用默认值 TypeAdapter
。
这正是我所做的:
public class EventTypeAdapterFactory implements TypeAdapterFactory {
private static final String TAG = EventTypeAdapterFactory.class.getSimpleName();
private Map<EventType, TypeAdapter<? extends Event>> ADAPTERS = new ArrayMap<>();
private TypeAdapter<Event> baseTypeAdapter;
private TypeAdapter<JsonElement> elementAdapter;
private TypeAdapter<EventType> eventTypeAdapter;
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!Event.class.isAssignableFrom(type.getRawType())) return null;
ADAPTERS.put(EventType.TYPE_A, gson.getDelegateAdapter(this, TypeToken.get(TypeAEvent.class)));
ADAPTERS.put(EventType.TYPE_B, gson.getDelegateAdapter(this, TypeToken.get(TypeBEvent.class)));
baseTypeAdapter = gson.getDelegateAdapter(this, TypeToken.get(Event.class));
elementAdapter = gson.getAdapter(JsonElement.class);
eventTypeAdapter = gson.getAdapter(EventType.class);
return (TypeAdapter<T>) new EventTypeAdapter().nullSafe();
}
private class EventTypeAdapter extends TypeAdapter<Event> {
@Override public void write(JsonWriter out, Event value) throws IOException {
EventType eventType = value.getType();
TypeAdapter<? extends Event> adapter = eventType == null ? baseTypeAdapter : ADAPTERS.get(eventType);
if (value instanceof TypeAEvent) {
writeWrap(adapter, out, (TypeAEvent) value, TypeAEvent.class);
} else if (value instanceof TypeBEvent) {
writeWrap(adapter, out, (TypeBEvent) value, TypeBEvent.class);
} else {
writeWrap(adapter, out, value, Event.class);
}
}
private <T extends Event> void writeWrap(TypeAdapter<? extends Event> adapter,
JsonWriter out, T value, Class<T> dummyForT) throws IOException {
((TypeAdapter<T>)adapter).write(out, value);
}
@Override public Event read(JsonReader in) throws IOException {
JsonObject objectJson = elementAdapter.read(in).getAsJsonObject();
JsonElement typeJson = objectJson.get("EventType");
EventType eventType = eventTypeAdapter.fromJsonTree(typeJson);
if (eventType == null) {
Log.w(TAG, "Unsupported EventType: " + typeJson);
}
TypeAdapter<? extends Event> adapter = eventType == null ? baseTypeAdapter : ADAPTERS.get(eventType);
return adapter.fromJsonTree(objectJson);
}
}
}
// EventType enum, change to reflect your values.
enum EventType {
TYPE_A, TYPE_B;
}
// Base Event type and its successors.
class Event {
@SerializedName("EventType")
private EventType type;
public EventType getType() {
return type;
}
}
class TypeAEvent extends Event {
@SerializedName("Data")
public String data;
}
class TypeBEvent extends Event {
@SerializedName("OtherData")
public int otherData;
}
与 colriot 的回答非常相似。我做了一些修改,以便我将 class 嵌入到 json 中,并且仅当它被序列化时 - 该地图有点难看。
它也不那么健壮,因为我很高兴在 null 上失败。
public final class ModelTypeAdapter extends TypeAdapter<Model> {
private static final String MODEL_CLASS_PROPERTY_NAME = "modelClass";
private final Gson gson;
private final TypeAdapterFactory containerFactory;
@SuppressWarnings("rawtypes")
private final TypeAdapter<Class> classTypeAdapter;
private final TypeAdapter<JsonElement> jsonElementAdapter;
public ModelTypeAdapter(final Gson gson, final TypeAdapterFactory containerFactory) {
this.gson = gson;
this.containerFactory = containerFactory;
this.classTypeAdapter = gson.getAdapter(Class.class);
this.jsonElementAdapter = gson.getAdapter(JsonElement.class);
}
@Override
public final void write(JsonWriter out, Model value) throws IOException {
doWrite(out, value);
}
private final <M extends Model> void doWrite(JsonWriter out, M value) throws IOException {
@SuppressWarnings("unchecked")
final Class<M> modelClass = (Class<M>) value.getClass();
final TypeAdapter<M> delegateAdapter = gson.getDelegateAdapter(containerFactory, TypeToken.get(modelClass));
final JsonObject jsonObject = delegateAdapter.toJsonTree(value).getAsJsonObject();
jsonObject.add(MODEL_CLASS_PROPERTY_NAME, classTypeAdapter.toJsonTree(modelClass));
jsonElementAdapter.write(out, jsonObject);
}
@Override
public final Model read(JsonReader in) throws IOException {
final JsonObject jsonObject = jsonElementAdapter.read(in).getAsJsonObject();
@SuppressWarnings("unchecked")
final Class<? extends Model> modelClass = classTypeAdapter.fromJsonTree(jsonObject.get(MODEL_CLASS_PROPERTY_NAME));
jsonObject.remove(MODEL_CLASS_PROPERTY_NAME);
final TypeAdapter<? extends Model> delegateAdapter = gson.getDelegateAdapter(containerFactory, TypeToken.get(modelClass));
return delegateAdapter.fromJsonTree(jsonObject);
}
}