Как сериализовать класс с интерфейсом?
Я никогда не делал много сериализации, но я пытаюсь использовать 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.
Я понимаю, почему это происходит, но мне трудно понять правильную схему борьбы с этим. Я смотрел в guide, но он не касался этого напрямую.
Ответы
Ответ 1
Поместите животное как переходное, оно не будет сериализовано.
Или вы можете сериализовать его самостоятельно, выполнив defaultWriteObject (...) и defaultReadObject (...) (я думаю, что то, что они назывались...)
РЕДАКТИРОВАТЬ См. раздел о написании создателя экземпляра здесь http://sites.google.com/site/gson/gson-user-guide
Gson can not десериализует интерфейс, так как он не знает, какой из классов реализации будет использоваться, поэтому вам нужно предоставить создателя экземпляра для вашего Animal и установить значение по умолчанию или подобное.
Ответ 2
Вот общее решение, которое работает во всех случаях, когда известен только интерфейс.
-
Создать сериализатор/десериализатор:
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();
Ответ 3
Решение @Maciek отлично работает, если тип объявленной переменной-члена является интерфейсом/абстрактным классом. Он не будет работать, если объявленный тип является классом sub-class/sub-interface/sub-abstract, если мы не зарегистрируем их все через 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 для 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);
}
Ответ 4
У меня была та же проблема, за исключением того, что мой интерфейс был примитивного типа (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);
}