在 Gson 中反序列化抽象类

发布于 2024-09-17 11:13:22 字数 217 浏览 18 评论 0原文

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

谢谢!

I have a tree object in JSON format I'm trying to deserialize with Gson. Each node contains its child nodes as fields of object type Node. Node is an interface, which has several concrete class implementations. During the deserialization process, how can I communicate to Gson which concrete class to implement when deserializing the node, if I do not know a priori which type the node belongs to? Each Node has a member field specifying the type. Is there a way to access the field when the object is in serialized form, and somehow communicate the type to Gson?

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

晌融 2024-09-24 11:13:22

我建议添加自定义 JsonDeserializer for Node

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

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

I'd suggest adding a custom JsonDeserializer for Nodes:

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

You will be able to access the JsonElement representing the node in the deserializer's method, convert that to a JsonObject, and retrieve the field that specifies the type. You can then create an instance of the correct type of Node based on that.

灯下孤影 2024-09-24 11:13:22

您需要注册 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();

You will need to register both JSONSerializer and JSONDeserializer. Also you can implement a generic adapter for all your interfaces in the following way:

  • During Serialization : Add a META-info of the actual impl class type.
  • During DeSerialization : Retrieve that meta info and call the JSONDeserailize of that class

Here is the implementation that I have used for myself and works fine.

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;
    }

}

Then you could register this adapter for all your interfaces as follows

Gson gson = new GsonBuilder()
        .registerTypeAdapter(IInterfaceOne.class,
                new PropertyBasedInterfaceMarshal())
        .registerTypeAdapter(IInterfaceTwo.class,
                new PropertyBasedInterfaceMarshal()).create();
心的位置 2024-09-24 11:13:22

我想稍微纠正一下上面的内容

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

    private static final String CLASS_TYPE = "CLASS_TYPE";

    @Override
    public Object deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {

        JsonObject jsonObj = jsonElement.getAsJsonObject();
       String className = jsonObj.get(CLASS_TYPE).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) {
    
        Gson gson = new Gson(); //without this line it will not work
        gson.toJson(object, object.getClass()); //and this one
        JsonElement jsonElement = gson.toJsonTree(object); //it needs to replace to another method...toJsonTree
        jsonElement.getAsJsonObject().addProperty(CLASS_TYPE, object.getClass().getCanonicalName());
        return jsonElement;
    }
}

然后我使用它:

Gson gson = new GsonBuilder()
                .registerTypeAdapter(AbstractTask.class, new PropertyMarshallerOfAbstractTask())
                .create();

然后我可以将List(我在其中保留一些从Abstract Task继承的非抽象类)解析为Json;

它的作用方向相反

List<AbstractTask> abstractTasks = gson.fromJson(json, new TypeToken<List<AbstractTask>>(){}.getType());

I want to correct the above a little

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

    private static final String CLASS_TYPE = "CLASS_TYPE";

    @Override
    public Object deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {

        JsonObject jsonObj = jsonElement.getAsJsonObject();
       String className = jsonObj.get(CLASS_TYPE).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) {
    
        Gson gson = new Gson(); //without this line it will not work
        gson.toJson(object, object.getClass()); //and this one
        JsonElement jsonElement = gson.toJsonTree(object); //it needs to replace to another method...toJsonTree
        jsonElement.getAsJsonObject().addProperty(CLASS_TYPE, object.getClass().getCanonicalName());
        return jsonElement;
    }
}

And then I use it:

Gson gson = new GsonBuilder()
                .registerTypeAdapter(AbstractTask.class, new PropertyMarshallerOfAbstractTask())
                .create();

And then I can parse List (where I keep some non-abstract classes, which inherited from Abstract Task) to Json;

And it works in the opposite direction

List<AbstractTask> abstractTasks = gson.fromJson(json, new TypeToken<List<AbstractTask>>(){}.getType());
醉梦枕江山 2024-09-24 11:13:22

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

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

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

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

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

问题记录在此处:

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

As far as I can tell this doesn't work for non-collection types, or more specifically, situations where the concrete type is used to serialize, and the interface type is used to deserialize. That is, if you have a simple class implementing an interface and you serialize the concrete class, then specify the interface to deserialize, you'll end up in an unrecoverable situation.

In the above example the type adapter is registered against the interface, but when you serialize using the concrete class it will not be used, meaning the CLASS_META_KEY data will never be set.

If you specify the adapter as a hierarchical adapter (thereby telling gson to use it for all types in the hierarchy), you'll end up in an infinite loop as the serializer will just keep calling itself.

Anyone know how to serialize from a concrete implementation of an interface, then deserialize using only the interface and an InstanceCreator?

By default it seems that gson will create the concrete instance, but does not set it's fields.

Issue is logged here:

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

七月上 2024-09-24 11:13:22

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

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

gson.toJson(foo, fooType);

gson.fromJson(json, fooType);

You have to use TypeToken class from Google Gson.
You will need of course has a generic class T to make it works

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

gson.toJson(foo, fooType);

gson.fromJson(json, fooType);
多孤肩上扛 2024-09-24 11:13:22

我无法使用上面的 TypeAdapter 解决方案,并且几乎放弃了序列化。最后我在类对象和Gson之间的中间步骤找到了解决方案。我将对象本身序列化为字节数组,然后 Gson 以类似的方式序列化字节数组+反序列化回来。

public class GenericSerializer {
  public static byte[] serializeForClass(final Object obj) {
    try {
      final var byteArrayOutputStream = new ByteArrayOutputStream();
      final var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
      objectOutputStream.writeObject(obj);
      objectOutputStream.flush();
      objectOutputStream.close();
      return byteArrayOutputStream.toByteArray();
    } catch (IOException e) {
      LOG.warn("Unable to serialize for class '%s'".formatted(obj.getClass().getName()), e);
      return null;
    }
  }

  public static <T> T deserializeForClass(final byte[] serializedObj, final Class<T> cls) {
    if (serializedObj == null) {
      return null;
    }
    try {
      final var inputStream = new ByteArrayInputStream(serializedObj);
      final var objectInputStream = new ObjectInputStream(inputStream);
      return cls.cast(objectInputStream.readObject());
    } catch (ClassNotFoundException | IOException | NullPointerException e) {
      LOG.warn("Unable to deserialize for class '%s'".formatted(cls), e);
      return null;
    }
  }

像这样使用


final var yourObject = new YourClass();
final var serialized = GenericSerializer.serializeForClass(yourObject);
final var gson = new Gson();
final var gsonSerialized = gson.toJson(serialized);
final var gsonDeserialized = gson.fromJson(gsonSerialized, byte[].class)
final var deserialized =  GenericSerializer.deserializeForClass(gsonDeserialized, YourClass.class);
assert(yourObject == deserialized);

我希望这可以像帮助我一样帮助别人。

I was unable to use the TypeAdapter solutions above, and had almost given up on serializing. In the end I found a solution in an intermediary step between the class object and Gson. I serialized the object itself into a byte array, and then Gson serialized the byte array + deserializing back in similar fashion.

public class GenericSerializer {
  public static byte[] serializeForClass(final Object obj) {
    try {
      final var byteArrayOutputStream = new ByteArrayOutputStream();
      final var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
      objectOutputStream.writeObject(obj);
      objectOutputStream.flush();
      objectOutputStream.close();
      return byteArrayOutputStream.toByteArray();
    } catch (IOException e) {
      LOG.warn("Unable to serialize for class '%s'".formatted(obj.getClass().getName()), e);
      return null;
    }
  }

  public static <T> T deserializeForClass(final byte[] serializedObj, final Class<T> cls) {
    if (serializedObj == null) {
      return null;
    }
    try {
      final var inputStream = new ByteArrayInputStream(serializedObj);
      final var objectInputStream = new ObjectInputStream(inputStream);
      return cls.cast(objectInputStream.readObject());
    } catch (ClassNotFoundException | IOException | NullPointerException e) {
      LOG.warn("Unable to deserialize for class '%s'".formatted(cls), e);
      return null;
    }
  }

Used like this


final var yourObject = new YourClass();
final var serialized = GenericSerializer.serializeForClass(yourObject);
final var gson = new Gson();
final var gsonSerialized = gson.toJson(serialized);
final var gsonDeserialized = gson.fromJson(gsonSerialized, byte[].class)
final var deserialized =  GenericSerializer.deserializeForClass(gsonDeserialized, YourClass.class);
assert(yourObject == deserialized);

I'm hoping this might help someone else the way it helped me.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文