我使用带有默认 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 对象树吗?
最佳答案
只有在反序列化数据中有
BaseType
或子类时才应运行自定义反序列化程序,而不是每个请求。你根据类型注册它,它只在 gson 需要序列化该类型时调用。您反序列化
BaseType
以及子类吗?如果是这样,这条线将扼杀你的表现 --return new Gson().fromJson(json, typeOfT);
创建新的 Gson
对象并不便宜。每次反序列化基类对象时都会创建一个。将此调用移至 BaseTypeDeserializer
的构造函数并将其存储在成员变量中将提高性能(假设您反序列化基类)。
- 创建
TypeAdapter
或TypeAdapterFactory
以根据字段选择类型的问题在于,您需要在开始使用流之前知道类型。如果类型字段是对象的一部分,则此时您无法知道类型。您链接到的帖子同样提到了 --
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 树,这样你就可以找到类型字段。与当前的实现相比,这不会为您节省太多。
如果您的子类相似并且它们之间没有任何字段冲突(名称相同但类型不同的字段),您可以使用包含所有字段的数据传输对象。使用 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/