java - GSON 类型适配器 : DeSerialize Polymorphic Objects Based on "Type" Field

标签 java android gson retrofit json-deserialization

我使用带有默认 Gson 解析器的 Retrofit 进行 JSON 处理。通常,我有一系列 4~5 个相关但略有不同的对象,它们都是公共(public)基础(我们称之为“BaseType”)的子类型。我知道我们可以通过检查“类型”字段将不同的 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 根据“类型”字段进行反序列化,而不必将整个 json 流读入 JsonElement 对象树吗?

最佳答案

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

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

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

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

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

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 实现可能会反射(reflect)您当前的实现,除了您要做的第一件事是自己转换为 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;
      }
    }
    

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

关于java - GSON 类型适配器 : DeSerialize Polymorphic Objects Based on "Type" Field,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33112274/

相关文章:

javafx - 如何正确使用高对比度主题?

java - Dagger 2 组件不生成用于构建的 Dagger 前缀类

java - 使用 CheckBox 禁用 View 后无法重新启用 View

java - 关于 JSF2 托管 Bean 作用域机制(请求作用域)的 Web 应用程序设计问题

java - 参数化数据结构以保存特定大小的数组?

android - 缩小 Android 应用程序但不要混淆它

java - 访问具有相同命名父级的值

java - 如何根据 JSON 对象的字段值创建特定 Java 类的对象?

java - 如何将 json 文件转换为 ArrayList<String>

java - 使用jaxb将xml解析为java对象时接收错误