【问题标题】:Deserializing an abstract class in Gson反序列化 Gson 中的抽象类
【发布时间】:2011-04-07 11:22:27
【问题描述】:

我有一个 JSON 格式的树对象,我正在尝试用 Gson 反序列化。每个节点都包含其子节点作为对象类型节点的字段。 Node 是一个接口,它有几个具体的类实现。在反序列化过程中,如果我不知道节点属于哪种类型,我如何与 Gson 通信在反序列化节点时要实现哪个具体类?每个节点都有一个指定类型的成员字段。有没有办法在对象处于序列化形式时访问该字段,并以某种方式将类型传达给 Gson?

谢谢!

【问题讨论】:

标签: java json abstract-class gson


【解决方案1】:

我建议为Nodes 添加自定义JsonDeserializer

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Node.class, new NodeDeserializer())
    .create();

您将能够在反序列化器的方法中访问代表节点的JsonElement,将其转换为JsonObject,并检索指定类型的字段。然后,您可以基于此创建正确类型的 Node 实例。

【讨论】:

  • @eng-carlin 如果它对你有用,通常会支持并接受答案,所以如果你能这样做,我会很感激。
  • 绿色复选标记已被点击。我会支持你,但我的声誉还不够高:(
  • @ColinD 我有一个与 GSON 相关的问题 here。如果可能的话可以帮助我。
  • 链接已失效。添加简短描述通常比添加链接更好:)
  • JsonDeserializer 文档指出:“新应用程序应该更喜欢 TypeAdapter,其流式 API 比此接口的树形 API 更高效。”仅供关注效率的任何人参考。
【解决方案2】:

您需要同时注册 JSONSerializer 和 JSONDeserializer。您还可以通过以下方式为所有接口实现通用适配器:

  • 序列化期间:添加实际 impl 类类型的 META-info。
  • 在反序列化期间:检索该元信息并调用该类的 JSONDeserailize

这是我自己使用的实现,效果很好。

public class PropertyBasedInterfaceMarshal implements
        JsonSerializer<Object>, JsonDeserializer<Object> {

    private static final String CLASS_META_KEY = "CLASS_META_KEY";

    @Override
    public Object deserialize(JsonElement jsonElement, Type type,
            JsonDeserializationContext jsonDeserializationContext)
            throws JsonParseException {
        JsonObject jsonObj = jsonElement.getAsJsonObject();
        String className = jsonObj.get(CLASS_META_KEY).getAsString();
        try {
            Class<?> clz = Class.forName(className);
            return jsonDeserializationContext.deserialize(jsonElement, clz);
        } catch (ClassNotFoundException e) {
            throw new JsonParseException(e);
        }
    }

    @Override
    public JsonElement serialize(Object object, Type type,
            JsonSerializationContext jsonSerializationContext) {
        JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass());
        jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY,
                object.getClass().getCanonicalName());
        return jsonEle;
    }

}

然后您可以为所有接口注册此适配器,如下所示

Gson gson = new GsonBuilder()
        .registerTypeAdapter(IInterfaceOne.class,
                new PropertyBasedInterfaceMarshal())
        .registerTypeAdapter(IInterfaceTwo.class,
                new PropertyBasedInterfaceMarshal()).create();

【讨论】:

  • IInterfaceOne.class 和 IInterfaceTwo.class 是什么意思
  • 感谢您为这个问题提供最优雅/可重用的解决方案。我像 mongodb 一样使用 _classCLASS_META_KEY
【解决方案3】:

据我所知,这不适用于非集合类型,或者更具体地说,不适用于具体类型用于序列化而接口类型用于反序列化的情况。也就是说,如果你有一个简单的类实现了一个接口,并且你序列化了具体的类,然后指定要反序列化的接口,你最终会陷入不可恢复的境地。

在上面的示例中,类型适配器是针对接口注册的,但是当您使用具体类进行序列化时,它不会被使用,这意味着永远不会设置 CLASS_META_KEY 数据。

如果您将适配器指定为分层适配器(从而告诉 gson 将其用于层次结构中的所有类型),您将陷入无限循环,因为序列化程序将继续调用自己。

有人知道如何从接口的具体实现序列化,然后只使用接口和 InstanceCreator 进行反序列化吗?

默认情况下,gson 似乎会创建具体实例,但不会设置它的字段。

问题记录在这里:

http://code.google.com/p/google-gson/issues/detail?id=411&q=interface

【讨论】:

    【解决方案4】:

    您必须使用来自 Google Gson 的 TypeToken 类。 您当然需要一个通用类 T 才能使其工作

    Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
    

    gson.toJson(foo, fooType);

    gson.fromJson(json, fooType);
    

    【讨论】:

    • 我尝试了你的建议,我得到了java.lang.UnsupportedOperationException: Abstract class can't be instantiated!
    • ConnectRecord 是抽象的,不能用于反序列化 Json
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-07
    • 1970-01-01
    • 1970-01-01
    • 2010-10-12
    • 2016-01-16
    • 1970-01-01
    相关资源
    最近更新 更多