如何使用接口序列化类?

IT小君   2021-10-21T02:36:21

我从未对序列化做过很多工作,但我正在尝试使用Google 的 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.

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

评论(4)
IT小君

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

  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();
    
2021-10-21T02:36:22   回复
IT小君

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

或者你可以通过实现defaultWriteObject(...)defaultReadObject(...)(我认为这就是他们所谓的......)

编辑请参阅此处有关“编写实例创建者”的部分

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

2021-10-21T02:36:22   回复
IT小君

如果成员变量声明类型是接口/抽象类,@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,如果你有另一种类型的自定义序列化并且它出现在对象中,它只会失败。

尽管如此,我还是希望有更好的解决方案。

参考

根据 Gson 2.3.1 文档JsonSerializationContextJsonDeserializationContext

在传递特定类型信息的指定对象上调用默认序列化。它不应在作为 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);
}
2021-10-21T02:36:22   回复
IT小君

我有同样的问题,除了我的接口是原始类型 ( 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);
}
2021-10-21T02:36:22   回复