Дезертирование абстрактного класса в Gson
У меня есть объект tree в формате JSON. Я пытаюсь десериализоваться с Gson. Каждый node содержит свои дочерние узлы как поля типа объекта Node. node - это интерфейс, который имеет несколько конкретных реализаций класса. Во время процесса десериализации, как я могу сообщить Gson, какой конкретный класс реализовать при десериализации node, если я не знаю априори, к какому типу принадлежит node? Каждый node имеет поле члена, задающее тип. Есть ли способ получить доступ к полю, когда объект находится в сериализованной форме, и каким-то образом передать этот тип Gson?
Спасибо!
Ответы
Ответ 1
Я бы предложил добавить JsonDeserializer для Node
s:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
Вы сможете получить доступ к JsonElement
, представляющему node в методе десериализатора, преобразовать его в JsonObject
и получить поле, которое указывает тип. Затем вы можете создать экземпляр правильного типа Node
на основе этого.
Ответ 2
Вам нужно будет зарегистрировать JSONSerializer и JSONDeserializer. Также вы можете реализовать универсальный адаптер для всех ваших интерфейсов следующим образом:
- Во время сериализации: добавьте META-информацию о фактическом типе типа impl.
- Во время DeSerialization: Получить эту метаинформацию и вызвать JSONDeserailize этого класса
Вот реализация, которую я использовал для себя и прекрасно работаю.
public class PropertyBasedInterfaceMarshal implements
JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_META_KEY = "CLASS_META_KEY";
@Override
public Object deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_META_KEY).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
@Override
public JsonElement serialize(Object object, Type type,
JsonSerializationContext jsonSerializationContext) {
JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass());
jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY,
object.getClass().getCanonicalName());
return jsonEle;
}
}
Затем вы можете зарегистрировать этот адаптер для всех своих интерфейсов следующим образом
Gson gson = new GsonBuilder()
.registerTypeAdapter(IInterfaceOne.class,
new PropertyBasedInterfaceMarshal())
.registerTypeAdapter(IInterfaceTwo.class,
new PropertyBasedInterfaceMarshal()).create();
Ответ 3
Насколько я могу судить, это не работает для типов, отличных от коллекции, или, более конкретно, ситуаций, когда конкретный тип используется для сериализации, а тип интерфейса используется для десериализации. То есть, если у вас есть простой класс, реализующий интерфейс, и вы сериализуете конкретный класс, тогда укажите интерфейс для десериализации, вы попадете в неустранимую ситуацию.
В приведенном выше примере адаптер типа зарегистрирован в интерфейсе, но при сериализации с использованием конкретного класса он не будет использоваться, то есть данные CLASS_META_KEY никогда не будут установлены.
Если вы укажете адаптер в качестве иерархического адаптера (тем самым сообщив gson о его использовании для всех типов в иерархии), вы окажетесь в бесконечном цикле, поскольку сериализатор будет просто продолжать называть себя.
Кто-нибудь знает, как сериализоваться из конкретной реализации интерфейса, а затем десериализовать, используя только интерфейс и экземпляр InstanceCreator?
По умолчанию кажется, что gson создаст конкретный экземпляр, но не установит его.
Проблема регистрируется здесь:
http://code.google.com/p/google-gson/issues/detail?id=411&q=interface
Ответ 4
Вы должны использовать класс TypeToken
из Google Gson.
Конечно, вам понадобится общий класс T, чтобы он работал.
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);