【问题标题】:Retrofit, Gson and an array of heterogeneous objectsRetrofit、Gson 和一系列异构对象
【发布时间】:2015-02-10 15:48:27
【问题描述】:

我正在使用Retrofit 向我们的服务器执行 REST 请求。其中一个请求返回一个对象的array,一旦在POJOs 中反序列化,就会从abstract class Event 扩展。事件具有返回String 的方法getEventType(),该字符串是键"EventType" 的值,我将始终在数组中的JSONObjects 中拥有它。

这就是JSON 的样子(我们现在有 7 种类型的对象):

[
 {
  "EventType":"typeA",
  "Data":"data"
 }, 
 {
  "EventType":"typeB",
  "OtherData":3
 }
] 

我正在尝试使用 RetrofitGSON APIs 在异步调用中反序列化此 JSON,以使用 Callback<List<Event>> 作为调用的参数,但我仍然不能想办法去做。

【问题讨论】:

  • 只使用一个包含所有可能成员的类? (数据,其他数据,...)
  • 这绝对是最后的选择。每个类都有多个变量和对象。
  • 那么“TypeA”、“TypeB”表示对象属于完全不同的类?
  • 所有类都继承自Event,但它们是非常不同的类,唯一的共同键是“EventType”。
  • 你可能想检查这些链接然后stackoverflow.com/questions/16590377/…stackoverflow.com/questions/6096940/… 他们都向你展示了如何编写一个反序列化器,你可以在那里检查 eventType 然后调用正确的构造函数。不过,这看起来像是大量的体力劳动。

标签: android json gson deserialization retrofit


【解决方案1】:

您可以为这种情况编写自定义 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;
}

【讨论】:

  • @David 我很高兴它有帮助!
  • 你是救世主!!
【解决方案2】:

我不确定,因为我没有对此进行测试,但是如果您编写这样的自定义反序列化器:

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;
        }
    }
}

然后在你用于改造的 Gson 反序列化器上设置这个,它可能会工作。

您可能必须将事件列表封装在另一个类中,例如

public class EventResponse {
    List<Event> events;
}

然后在你的界面中使用它作为参数,但我不确定。

【讨论】:

    【解决方案3】:

    与 colriot 的回答非常相似。我进行了一些修改,以便将一个类嵌入到 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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-12
      • 2018-03-28
      • 1970-01-01
      • 2016-08-07
      • 1970-01-01
      • 1970-01-01
      • 2015-09-27
      • 2016-04-09
      相关资源
      最近更新 更多