Gson反序列化泛型类型适配器的基类

时间:2018-02-28 16:23:29

标签: java json gson

我有以下课程:

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帮助我实现了这个功能。

2 个答案:

答案 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); } } } }
  • 对于后者,你会遇到无限递归(如果我没错:因为上下文只提供了反序列化相同类型实例的机制)。 / LI>

以下类型的适配器工厂可以克服上述限制:

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并未按照您的示例分隔propertiesType。< / 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用户)。

但如果不能,那么我建议包装类。

然而,正如你所看到的那样,你自己编码并不是那么重要。