【问题标题】:How to serialize a class with an interface?如何用接口序列化一个类?
【发布时间】:2011-06-15 06:51:28
【问题描述】:

我从未对序列化做过很多,但我尝试使用Google's gson 将Java 对象序列化为文件。这是我的问题的一个例子:

public interface Animal {
    public String getName();
}


 public class Cat implements Animal {

    private String mName = "Cat";
    private String mHabbit = "Playing with yarn";

    public String getName() {
        return mName;
    }

    public void setName(String pName) {
        mName = pName;
    }

    public String getHabbit() {
        return mHabbit;
    }

    public void setHabbit(String pHabbit) {
        mHabbit = pHabbit;
    }

}

public class Exhibit {

    private String mDescription;
    private Animal mAnimal;

    public Exhibit() {
        mDescription = "This is a public exhibit.";
    }

    public String getDescription() {
        return mDescription;
    }

    public void setDescription(String pDescription) {
        mDescription = pDescription;
    }

    public Animal getAnimal() {
        return mAnimal;
    }

    public void setAnimal(Animal pAnimal) {
        mAnimal = pAnimal;
    }

}

public class GsonTest {

public static void main(String[] argv) {
    Exhibit exhibit = new Exhibit();
    exhibit.setAnimal(new Cat());
    Gson gson = new Gson();
    String jsonString = gson.toJson(exhibit);
    System.out.println(jsonString);
    Exhibit deserializedExhibit = gson.fromJson(jsonString, Exhibit.class);
    System.out.println(deserializedExhibit);
}
}

所以这很好地序列化了——但可以理解的是,在 Animal 上丢弃了类型信息:

{"mDescription":"This is a public exhibit.","mAnimal":{"mName":"Cat","mHabbit":"Playing with yarn"}}

这会给反序列化带来真正的问题,但是:

Exception in thread "main" java.lang.RuntimeException: No-args constructor for interface com.atg.lp.gson.Animal does not exist. Register an InstanceCreator with Gson for this type to fix this problem.

我明白为什么会发生这种情况,但无法找出处理此问题的正确模式。我确实查看了guide,但它没有直接解决这个问题。

【问题讨论】:

    标签: java json interface gson


    【解决方案1】:

    将动物设为transient,则不会被序列化。

    或者你可以通过实现defaultWriteObject(...)defaultReadObject(...) 自己序列化它(我想这就是他们所谓的......)

    编辑参见“编写实例创建者”here部分。

    Gson 无法反序列化接口,因为它不知道将使用哪个实现类,因此您需要为您的 Animal 提供实例创建器并设置默认值或类似值。

    【讨论】:

    • 我确实希望 Animal 被序列化/反序列化。您能否更具体地了解那些 Write 和 Read 方法?谢谢。
    • 对不起,我正在考虑要覆盖的标准序列化程序方法。我看到gson有google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/…,你可以试试,你可以覆盖它的默认反序列化。
    • 我认为我需要的不仅仅是反序列化器,因为 Animal 没有使用任何类型信息进行序列化。假设有类型信息,谁来负责反序列化实现类?
    • 我不确定。序列化作品,因为你在一个实际的类中传递它,猫,但反序列化失败,因为展览类只有一个动物,gson不知道要恢复哪个实现类。您可以为 Animal 实现 JsonDeserializer,您可以在其中提供自己的逻辑来制作 Cat 或 Dog 或其他东西。该解决方案不是很漂亮,但它确实暗示您应该只序列化纯数据类,而不是涉及继承和多态的类。
    • 是的,我最终基本上做到了——让为展览序列化的类提取实现动物的类,序列化类名,然后在该类上调用适当的序列化程序来序列化数据。当我反序列化时,我使用反射来实例化适当的反序列化器。我非常确信我让它变得比它需要的更复杂,但另一方面,它确实有效。 =)
    【解决方案2】:

    我遇到了同样的问题,只是我的界面是原始类型 (CharSequence) 而不是 JsonObject:

    if (elem instanceof JsonPrimitive){
        JsonPrimitive primitiveObject = (JsonPrimitive) elem;
    
        Type primitiveType = 
        primitiveObject.isBoolean() ?Boolean.class : 
        primitiveObject.isNumber() ? Number.class :
        primitiveObject.isString() ? String.class :
        String.class;
    
        return context.deserialize(primitiveObject, primitiveType);
    }
    
    if (elem instanceof JsonObject){
        JsonObject wrapper = (JsonObject) elem;         
    
        final JsonElement typeName = get(wrapper, "type");
        final JsonElement data = get(wrapper, "data");
        final Type actualType = typeForName(typeName); 
        return context.deserialize(data, actualType);
    }
    

    【讨论】:

      【解决方案3】:
      如果成员变量的声明类型是接口/抽象类,则

      @Maciek 解决方案非常有效。如果声明的类型是子类/子接口/子抽象类,它将不起作用,除非我们通过registerTypeAdapter()将它们全部注册。使用registerTypeHierarchyAdapter我们可以避免一一注册,但我意识到它会因为无限循环而导致StackOverflowError。 (请阅读下面的参考部分)

      简而言之,我的解决方案看起来有点无意义,但没有StackOverflowError 也可以。

      @Override
      public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
          final JsonObject wrapper = new JsonObject();
          wrapper.addProperty("type", object.getClass().getName());
          wrapper.add("data", new Gson().toJsonTree(object));
          return wrapper;
      }
      

      我使用另一个新的Gson 工作实例作为默认序列化器/解串器以避免无限循环。这个解决方案的缺点是你也会丢失其他的TypeAdapter,如果你有另一种类型的自定义序列化并且它出现在对象中,它就会失败。

      不过,我希望有更好的解决方案。

      参考

      根据JsonSerializationContextJsonDeserializationContext 的Gson 2.3.1 文档

      在传递特定类型信息的指定对象上调用默认序列化。永远不应在作为 JsonSerializer.serialize(Object, Type, JsonSerializationContext) 方法的参数接收的元素上调用它。这样做会导致无限循环,因为 Gson 会再次调用自定义序列化程序。

      调用指定对象的默认反序列化。永远不应在作为 JsonDeserializer.deserialize(JsonElement, Type, JsonDeserializationContext) 方法的参数接收的元素上调用它。这样做将导致无限循环,因为 Gson 将再次调用自定义反序列化器。

      由此得出结论,以下实现将导致无限循环并最终导致StackOverflowError

      @Override
      public JsonElement serialize(Animal src, Type typeOfSrc,
              JsonSerializationContext context) {
          return context.serialize(src);
      }
      

      【讨论】:

        【解决方案4】:

        这是一个通用解决方案,适用于仅静态已知接口的所有情况。

        1. 创建序列化器/反序列化器:

          final class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {
              public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
                  final JsonObject wrapper = new JsonObject();
                  wrapper.addProperty("type", object.getClass().getName());
                  wrapper.add("data", context.serialize(object));
                  return wrapper;
              }
          
              public T deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
                  final JsonObject wrapper = (JsonObject) elem;
                  final JsonElement typeName = get(wrapper, "type");
                  final JsonElement data = get(wrapper, "data");
                  final Type actualType = typeForName(typeName); 
                  return context.deserialize(data, actualType);
              }
          
              private Type typeForName(final JsonElement typeElem) {
                  try {
                      return Class.forName(typeElem.getAsString());
                  } catch (ClassNotFoundException e) {
                      throw new JsonParseException(e);
                  }
              }
          
              private JsonElement get(final JsonObject wrapper, String memberName) {
                  final JsonElement elem = wrapper.get(memberName);
                  if (elem == null) throw new JsonParseException("no '" + memberName + "' member found in what was expected to be an interface wrapper");
                  return elem;
              }
          }
          
        2. 让 Gson 将其用于您选择的接口类型:

          Gson gson = new GsonBuilder().registerTypeAdapter(Animal.class, new InterfaceAdapter<Animal>())
                                       .create();
          

        【讨论】:

        • StackOverflow 异常 -- context.serialize(object) 调用自身。不知道这对你有什么作用。
        • @Hoten:这是可行的,因为 GSON 在选择序列化程序时会同时考虑声明的类型和实际类型。当字段类型为Animal 时,GSON 将优先选择Animal 接口的类型适配器,如果已注册,即使实际类型为Cat。然而,在context.serialize(object) 调用中,没有“声明类型”需要考虑,它只是传递了一个Cat,因此不会使用类型适配器。
        • @Ryan 是对的,context.serailize(object) 最终陷入无限循环 - 你们其他人的效果如何?
        • 这似乎对我不起作用。我得到com.google.gson.JsonParseException: no 'type' member found in json file.
        • 我收到此错误com.google.gson.JsonParseException: no 'type' member found in what was expected to be an interface wrapper
        猜你喜欢
        • 1970-01-01
        • 2010-12-08
        • 2011-11-30
        • 2022-11-22
        • 1970-01-01
        • 1970-01-01
        • 2012-06-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多