我有以下课程:
public class Kit {
private String name;
private int num;
}
我有一个类,它扩展了Kit的附加功能:
public class ExtendedKit extends Kit {
private String extraProperty;
}
使用Gson,我希望能够对这两个类以及更多不同类型进行反序列化,而无需为它们创建一堆类型适配器,因为它们都具有相同的Json结构:
{
"type": "com.driima.test.ExtendedKit",
"properties": {
"name": "An Extended Kit",
"num": 124,
"extra_property": "An extra property"
}
}
将其传递给注册到我的GsonBuilder的以下类型适配器:
public class GenericAdapter<T> implements JsonDeserializer<T> {
@Override
public T deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
final JsonObject object = json.getAsJsonObject();
String classType = object.get("type").getAsString();
JsonElement element = object.get("properties");
try {
return context.deserialize(element, Class.forName(classType));
} catch (ClassNotFoundException e) {
throw new JsonParseException("Unknown element type: " + type, e);
}
}
}
事情是,这适用于ExtendedKit
,但如果我想反序列化Kit
而没有extraProperty,它就不起作用,因为它调用时会导致NullPointerException在属性对象上调用context.deserialize()。有什么方法可以解决这个问题吗?
以下是我使用的GsonBuilder的代码:
private static final GsonBuilder GSON_BUILDER = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapterFactory(new PostProcessExecutor())
.registerTypeAdapter(Kit.class, new GenericAdapter<Kit>());
注意:添加了PostProcessExecutor,以便我可以对我可以进行后处理的反序列化的任何对象应用后处理。有一篇文章here帮助我实现了这个功能。
答案 0 :(得分:1)
我不认为JsonDeserializer
在这里是个不错的选择:
Gson
GsonBuilder
中的registerTypeHierarchyAdapter
实例,这种实例容易出错,或者使用final class PolymorphicTypeAdapterFactory
implements TypeAdapterFactory {
// Let's not hard-code `Kit.class` here and let a user pick up types at a call-site
private final Predicate<? super Class<?>> predicate;
private PolymorphicTypeAdapterFactory(final Predicate<? super Class<?>> predicate) {
this.predicate = predicate;
}
static TypeAdapterFactory get(final Predicate<? super Class<?>> predicate) {
return new PolymorphicTypeAdapterFactory(predicate);
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final Class<? super T> rawClass = typeToken.getRawType();
if ( !predicate.test(rawClass) ) {
// Something we cannot handle? Try pick the next best type adapter factory
return null;
}
// This is what JsonDeserializer fails at:
final TypeAdapter<T> writeTypeAdapter = gson.getDelegateAdapter(this, typeToken);
// Despite it's possible to use the above type adapter for both read and write, what if the `type` property points to another class?
final Function<? super Class<T>, ? extends TypeAdapter<T>> readTypeAdapterResolver = actualRawClass -> {
if ( !rawClass.isAssignableFrom(actualRawClass) ) {
throw new IllegalStateException("Cannot parse as " + actualRawClass);
}
return gson.getDelegateAdapter(this, TypeToken.get(actualRawClass));
};
return PolymorphicTypeAdapter.get(rawClass, writeTypeAdapter, readTypeAdapterResolver);
}
private static final class PolymorphicTypeAdapter<T>
extends TypeAdapter<T> {
private final Class<? super T> rawClass;
private final TypeAdapter<T> writeTypeAdapter;
private final Function<? super Class<T>, ? extends TypeAdapter<T>> readTypeAdapterResolver;
private PolymorphicTypeAdapter(final Class<? super T> rawClass, final TypeAdapter<T> writeTypeAdapter,
final Function<? super Class<T>, ? extends TypeAdapter<T>> readTypeAdapterResolver) {
this.rawClass = rawClass;
this.writeTypeAdapter = writeTypeAdapter;
this.readTypeAdapterResolver = readTypeAdapterResolver;
}
// Since constructors are meant only to assign parameters to fields, encapsulate the null-safety handling in the factory method
private static <T> TypeAdapter<T> get(final Class<? super T> rawClass, final TypeAdapter<T> writeTypeAdapter,
final Function<? super Class<T>, ? extends TypeAdapter<T>> readTypeAdapterResolver) {
return new PolymorphicTypeAdapter<>(rawClass, writeTypeAdapter, readTypeAdapterResolver)
.nullSafe();
}
@Override
@SuppressWarnings("resource")
public void write(final JsonWriter jsonWriter, final T value)
throws IOException {
jsonWriter.beginObject();
jsonWriter.name("type");
jsonWriter.value(rawClass.getName());
jsonWriter.name("properties");
writeTypeAdapter.write(jsonWriter, value);
jsonWriter.endObject();
}
@Override
public T read(final JsonReader jsonReader)
throws IOException {
jsonReader.beginObject();
// For simplicity's sake, let's assume that the class property `type` always precedes the `properties` property
final Class<? super T> actualRawClass = readActualRawClass(jsonReader);
final T value = readValue(jsonReader, actualRawClass);
jsonReader.endObject();
return value;
}
private Class<? super T> readActualRawClass(final JsonReader jsonReader)
throws IOException {
try {
requireProperty(jsonReader, "type");
final String value = jsonReader.nextString();
@SuppressWarnings("unchecked")
final Class<? super T> actualRawClass = (Class<? super T>) Class.forName(value);
return actualRawClass;
} catch ( final ClassNotFoundException ex ) {
throw new AssertionError(ex);
}
}
private T readValue(final JsonReader jsonReader, final Class<? super T> rawClass)
throws IOException {
requireProperty(jsonReader, "properties");
@SuppressWarnings("unchecked")
final Class<T> castRawClass = (Class<T>) rawClass;
final TypeAdapter<T> readTypeAdapter = readTypeAdapterResolver.apply(castRawClass);
return readTypeAdapter.read(jsonReader);
}
private static void requireProperty(final JsonReader jsonReader, final String propertyName)
throws IOException {
final String name = jsonReader.nextName();
if ( !name.equals(propertyName) ) {
throw new JsonParseException("Unexpected property: " + name);
}
}
}
}
。以下类型的适配器工厂可以克服上述限制:
Kit
仅用于Kit
类的使用示例(下面的方法参考仅检查Kit
是否是给定实际原始类的超类,或者后者是private static final Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapterFactory(PolymorphicTypeAdapterFactory.get(Kit.class::isAssignableFrom))
.create();
本身):
RuntimeTypeAdapterFactory
请注意,您的问题不是唯一的,您的案例几乎涵盖RuntimeTypeAdapterFactory
,但type
并未按照您的示例分隔properties
和Type
。< / p>
PS 请注意,此类型适配器工厂远不是真正的通用:它不适用于类型(类是类型的特殊情况),通用类型等。如果感兴趣,但当然不是过度工程,您可能希望使用normal
实例object serialization mechanism引用我的参数化编码类型的解决方案(太神秘并且与特定平台紧密绑定)或使用类型和泛型类型表示法parsing using JParsec(两个链接都指的是讲俄语的StackExchange站点)。
答案 1 :(得分:0)
我认为适配器中的一个问题是它实际上永远不会仅为ExtendedKit
调用Kit
。这就是为什么与<{1}}一起工作的原因,我猜。由于类型擦除,Gson无论如何都无法处理泛型。
尽管如此:像Json一样,声明要反序列化的对象是一个很好而且明确的做法,因为它通常会减少适配器等逻辑的编码......
我建议您为ExtendedKit
声明一个包装类,如:
Kit
使用@Getter
@AllArgsConstructor
public class KitWrapper {
private String type;
@SerializedName("properties") //
private Kit kit;
}
更容易反序列化:
TypeAdapter
请注册此适配器并获取Kit:
@Slf4j
public class KitWrapperAdapter implements JsonDeserializer<KitWrapper> {
@Override
public KitWrapper deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
try {
@SuppressWarnings("unchecked")
Class<? extends Kit> classKit =
(Class<? extends Kit>)Class.forName(json.getAsJsonObject()
.get("type").getAsString() );
JsonElement jeProperties = json.getAsJsonObject().get("properties");
Kit kit = context.deserialize(jeProperties, classKit);
// Not needed to parse anymore, new KitWrapper can be created
// with this information
return new KitWrapper(classKit.getName(), kit);
} catch (Exception e) {
log.error("{}", e.toString());
return null;
}
}
}
正如his answer中提到的 Lyubomyr Shaydariv ,Kit kit = kitWrapper.getKit();
的案例几乎适合。似乎RunTimeAdapterFactory
应该是反序列化对象的属性,而不是它是顶级属性而实际对象是较低级别。换句话说,如果你可以改变你的Json&amp; type
相应地:
Kit
它可能正常工作。在这种情况下,您可能会对How to add Gson extras to an Android project?感兴趣(即使不是Maven或Gradle用户)。
但如果不能,那么我建议包装类。
然而,正如你所看到的那样,你自己编码并不是那么重要。