GSON TypeAdapter:基于“类型”字段的DeSerialize多态对象

时间:2015-10-13 20:44:06

标签: java android gson retrofit json-deserialization

我正在使用Retrofit和默认的Gson解析器进行JSON处理。通常,我有一系列4~5个相关但略有不同的对象,它们都是共同基础的子类型(我们称之为“BaseType”)。我知道我们可以通过选中“type”字段将不同的JSON反序列化到各自的子模型。最常用的方法是扩展JsonDeserializer并将其注册为Gson实例中的类型适配器:

class BaseTypeDeserializer implements JsonDeserializer<BaseType> {
    private static final String TYPE_FIELD = "type";

    @Override
    public BaseType 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 ("type_a".equals(type)) {
                return context.deserialize(json, AType.class);
            } else if ("type_b".equals(type)) {
                return context.deserialize(json, BType.class);
            } ...

            // If you need to deserialize as BaseType,
            // deserialize without the current context
            // or you will infinite loop
            return new Gson().fromJson(json, typeOfT);

        } else {
            // Return a blank object on error
            return new BaseType();
        }
    }
}

但是,根据我的经验,这非常慢,似乎是因为我们必须将整个JSON文档加载到JsonElement中,然后遍历它以查找类型字段。我也不喜欢这个反序列化器必须在我们的每个REST调用上运行,即使数据并不总是必须映射到BaseType(或其子类)。

这个foursquare blog post提到使用TypeAdapters作为替代方案,但它并没有真正进一步的例子。

这里的任何人都知道如何使用TypeAdapterFactory基于'type'字段反序列化,而不必将整个json流读入JsonElement对象树?

1 个答案:

答案 0 :(得分:3)

  1. 只有在反序列化数据中有BaseType或子类时,才应运行自定义反序列化程序,而不是每个请求。您可以根据类型进行注册,只有在gson需要序列化该类型时才会调用它。

  2. 您是否反序列化BaseType以及子类?如果是这样,这条线将会破坏你的表现 -

    return new Gson().fromJson(json, typeOfT);

  3. 创建新的Gson对象并不便宜。每次反序列化基类对象时都要创建一个。将此调用移动到BaseTypeDeserializer的构造函数并将其存储在成员变量中将提高性能(假设您对基类进行反序列化)。

    1. 根据字段选择类型创建TypeAdapterTypeAdapterFactory的问题是,在开始使用流之前,您需要知道类型。如果类型字段是对象的一部分,则您无法知道该点的类型。你链接的帖子提到了 -
    2.   

      使用TypeAdapter编写的反序列化程序可能不如   那些用JsonDeserializers编写的。想象一下,你想要一个类型字段   确定对象字段反序列化的内容。随着流媒体   API,您需要保证该类型在之前的响应中出现   对象

      如果你可以在JSON流中获取对象之前的类型,你可以这样做,否则你的TypeAdapter实现可能会镜像你当前的实现,除了你做的第一件事是转换为Json树自己,所以你可以找到类型字段。这不会为您当前的实施节省太多。

      1. 如果您的子类很相似,并且它们之间没有任何字段冲突(具有相同名称但类型不同的字段),则可以使用包含所有字段的数据传输对象。使用gson对其进行反序列化,然后使用它创建对象。

        public class MyDTO {
           String type;
           // Fields from BaseType
           String fromBase;
           // Fields from TypeA
           String fromA;
           // Fields from TypeB
           // ...
        }
        
        
        public class BaseType {
          String type;
          String fromBase;
        
          public BaseType(MyDTO dto) {
            type = dto.type;
            fromBase = dto.fromBase;
          }
        }
        
        public class TypeA extends BaseType {
          String fromA;
        
          public TypeA(MyDTO dto) {
            super(dto);
            fromA = dto.fromA;
          }
        }
        
      2. 然后,您可以创建一个TypeAdapterFactory来处理从DTO到您的对象的转换 -

        public class BaseTypeAdapterFactory implements TypeAdapterFactory {
        
          public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
            if(BaseType.class.isAssignableFrom(type.getRawType())) {
              TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
              return newItemAdapter((TypeAdapter<BaseType>) delegate,
                  gson.getAdapter(new TypeToken<MyDTO>(){}));
            } else {
              return null;
            }
          }
        
          private TypeAdapter newItemAdapter(
              final TypeAdapter<BaseType> delagateAdapter,
              final TypeAdapter<MyDTO> dtoAdapter) {
            return new TypeAdapter<BaseType>() {
        
              @Override
              public void write(JsonWriter out, BaseType value) throws IOException {
                delagateAdapter.write(out, value);
              }
        
              @Override
              public BaseType read(JsonReader in) throws IOException {
                MyDTO dto = dtoAdapter.read(in);
                if("base".equals(dto.type)) {
                  return new BaseType(dto);
                } else if ("type_a".equals(dto.type)) {
                  return new TypeA(dto);
                } else {
                  return null;
                }
              }
            };
          }
        }
        

        并像这样使用 -

        Gson gson = new GsonBuilder()
            .registerTypeAdapterFactory(new BaseTypeAdapterFactory())
            .create();
        
        BaseType base = gson.fromJson(baseString, BaseType.class);