GSON TypeAdapter:基于"Type"字段反序列化多态对象

GSON TypeAdapter: DeSerialize Polymorphic Objects Based on "Type" Field

我使用带有默认 Gson 解析器的 Retrofit 进行 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. 当反序列化数据中有 BaseType 或子 class 时,自定义反序列化器应该只 运行,而不是每个请求。你根据类型注册它,只有当gson需要序列化该类型时才会调用它。

  2. 你反序列化 BaseType 以及子 classes 吗?如果是这样,这条线将扼杀你的表现 --

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

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

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

Deserializers written using TypeAdapters may be less flexible than those written with JsonDeserializers. Imagine you want a type field to determine what an object field deserializes to. With the streaming API, you need to guarantee that type comes down in the response before object.

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

  1. 如果你的子class是相似的,并且它们之间没有任何字段冲突(名称相同但类型不同的字段),你可以使用数据传输对象具有所有字段。使用 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;
      }
    }
    

然后您可以创建一个 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);