Как сериализовать класс с интерфейсом?
Я никогда не делал много с сериализацией, но пытаюсь использовать gson от Google для сериализации 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 ответа
Поместите животное как преходящее, тогда оно не будет сериализовано.
Или вы можете сериализовать его самостоятельно, реализовав defaultWriteObject(...) и defaultReadObject(...) (я думаю, так они назывались...)
РЕДАКТИРОВАТЬ См. Раздел "Написание создателя экземпляра" здесь http://sites.google.com/site/gson/gson-user-guide
Gson не может десериализовать интерфейс, так как он не знает, какой реализующий класс будет использоваться, поэтому вам нужно предоставить создателя экземпляра для вашего Animal и установить значение по умолчанию или подобное.
Вот общее решение, которое работает для всех случаев, когда статически известен только интерфейс.
Создать сериализатор / десериализатор:
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();
Решение @Maciek прекрасно работает, если объявленный тип переменной-члена является интерфейсом / абстрактным классом. Это не будет работать, если объявленный тип является подклассом / субинтерфейсом / субабстрактным классом, если мы не зарегистрируем их все через registerTypeAdapter()
, Мы можем избежать регистрации по одному с использованием registerTypeHierarchyAdapter
, но я понимаю, что это вызовет StackruError
из-за бесконечного цикла. (Пожалуйста, прочитайте справочный раздел ниже)
Короче говоря, мое обходное решение выглядит немного бессмысленным, но оно работает без StackruError
,
@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, в свою очередь, снова вызовет пользовательский десериализатор.
Это делает вывод, что приведенная ниже реализация вызовет бесконечный цикл и вызовет StackruError
в конце концов.
@Override
public JsonElement serialize(Animal src, Type typeOfSrc,
JsonSerializationContext context) {
return context.serialize(src);
}
У меня была такая же проблема, за исключением того, что мой интерфейс был примитивного типа (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);
}