Дезерминирование полиморфных типов с Джексоном
Если у меня есть такая структура класса:
public abstract class Parent {
private Long id;
...
}
public class SubClassA extends Parent {
private String stringA;
private Integer intA;
...
}
public class SubClassB extends Parent {
private String stringB;
private Integer intB;
...
}
Есть ли альтернативный способ десериализации разных, чем @JsonTypeInfo
? Используя эту аннотацию в моем родительском классе:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "objectType")
Я бы предпочел не принуждать клиентов моего API включать "objectType": "SubClassA"
для десериализации подкласса Parent
.
Вместо использования @JsonTypeInfo
делает ли Джексон способ аннотировать подкласс и отличать его от других подклассов с помощью уникального свойства? В приведенном выше примере это будет примерно так: "Если объект JSON имеет "stringA": ...
десериализует его как SubClassA
, если он имеет "stringB": ...
десериализует его как SubClassB
".
Ответы
Ответ 1
Это похоже на то, что нужно использовать @JsonTypeInfo
и @JsonSubTypes
, но я просмотрел документы, и ни одно из свойств, которые могут быть предоставлены, похоже, похоже на то, что вы описываете.
Вы можете написать собственный десериализатор, который использует свойства @JsonSubTypes
' "name" и "value" нестандартным способом для выполнения того, что вы хотите. Десериализатор и @JsonSubTypes
будут поставляться в базовом классе, а десериализатор будет использовать значения "name" для проверки наличия свойства и, если он существует, затем десериализовать JSON в класс, указанный в свойстве "значение", Тогда ваши классы будут выглядеть примерно так:
@JsonDeserialize(using = PropertyPresentDeserializer.class)
@JsonSubTypes({
@Type(name = "stringA", value = SubClassA.class),
@Type(name = "stringB", value = SubClassB.class)
})
public abstract class Parent {
private Long id;
...
}
public class SubClassA extends Parent {
private String stringA;
private Integer intA;
...
}
public class SubClassB extends Parent {
private String stringB;
private Integer intB;
...
}
Ответ 2
Нет. Такая функция была запрошена - ее можно было бы назвать "вывод типа" или "подразумеваемый тип", но никто не придумал обоснованного общего предложения о том, как это должно работать. Легко думать о способах поддержки конкретных решений для конкретных случаев, но выяснение общего решения сложнее.
Ответ 3
Мое приложение требует от меня сохранения старой структуры, поэтому я нашел способ поддерживать полиморфизм без изменения данных. Вот что я делаю:
- Расширить JsonDeserializer
-
Преобразуйте в Дерево и прочитайте поле, затем верните объект подкласса
@Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode jsonNode = p.readValueAsTree();
Iterator<Map.Entry<String, JsonNode>> ite = jsonNode.fields();
boolean isSubclass = false;
while (ite.hasNext()) {
Map.Entry<String, JsonNode> entry = ite.next();
// **Check if it contains field name unique to subclass**
if (entry.getKey().equalsIgnoreCase("Field-Unique-to-Subclass")) {
isSubclass = true;
break;
}
}
if (isSubclass) {
return mapper.treeToValue(jsonNode, SubClass.class);
} else {
// process other classes
}
}
Ответ 4
Здесь решение, которое я придумал, немного расширилось для Эрика Гиллеспи. Он делает именно то, что вы просили, и это сработало для меня.
Использование Jackson 2.9
@JsonDeserialize(using = CustomDeserializer.class)
public abstract class BaseClass {
private String commonProp;
}
// Important to override the base class' usage of CustomDeserializer which produces an infinite loop
@JsonDeserialize(using = JsonDeserializer.None.class)
public class ClassA extends BaseClass {
private String classAProp;
}
@JsonDeserialize(using = JsonDeserializer.None.class)
public class ClassB extends BaseClass {
private String classBProp;
}
public class CustomDeserializer extends StdDeserializer<BaseClass> {
protected CustomDeserializer() {
super(BaseClass.class);
}
@Override
public BaseClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
TreeNode node = p.readValueAsTree();
// Select the concrete class based on the existence of a property
if (node.get("classAProp") != null) {
return p.getCodec().treeToValue(node, ClassA.class);
}
return p.getCodec().treeToValue(node, ClassB.class);
}
}
// Example usage
String json = ...
ObjectMapper mapper = ...
BaseClass instance = mapper.readValue(json, BaseClass.class);
Если вы хотите стать более привлекательным, вы можете расширить CustomDeserializer
чтобы включить Map<String, Class<?>>
который отображает имя свойства, которое при наличии сопоставляется с определенным классом. Такой подход представлен в этой статье.
Кстати, существует проблема github, запрашивающая это здесь: https://github.com/FasterXML/jackson-databind/issues/1627