我从未对序列化做过多少工作,但我尝试使用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,但没有直接解决这个问题。
答案 0 :(得分:78)
这是一个通用的解决方案,适用于只有静态知道接口的所有情况。
创建序列化器/反序列化器:
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;
}
}
让Gson将它用于您选择的接口类型:
Gson gson = new GsonBuilder().registerTypeAdapter(Animal.class, new InterfaceAdapter<Animal>())
.create();
答案 1 :(得分:13)
将动物放置为transient
,然后不会将其序列化。
或者您可以通过实施defaultWriteObject(...)
和defaultReadObject(...)
来自行序列化(我认为这就是所谓的......)
编辑请参阅有关“编写实例创建者”的部分here。
Gson无法反序列化接口,因为它不知道将使用哪个实现类,因此您需要为Animal提供实例创建器并设置默认值或类似值。
答案 2 :(得分:4)
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
,如果你有另一种类型的自定义序列化它出现在对象中,它就会失败。
不过,我希望有更好的解决方案。
根据JsonSerializationContext
和JsonDeserializationContext
在指定对象上调用默认序列化,传递特定类型信息。永远不应该在作为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);
}
答案 3 :(得分:-1)
我有同样的问题,除了我的界面是原始类型(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);
}