Учитывая следующие классы (геттеры, сеттеры и абстрактные реализации опущены для краткости) ...
public class Matcher {
private String name;
private Operation match;
}
public class MatcherDTO {
private String name;
private Operation matchExpression;
}
public abstract class Operation {
// Parameters to the operation, which may be
// Operation implementations or boxed
// primitives
private List<Object> inputs;
public abstract Class getResultType();
...
}
public class AttributeReference extends Operation {
...
}
public class Equals extends Operation {
...
}
...
... Я собрал следующую MapperFactory:
@Bean
public MapperFactory mapperFactory() {
final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Matcher.class, MatcherDTO.class)
.field("match", "matchExpression")
.byDefault()
.register();
mapperFactory.classMap(Operation.class, Operation.class)
.exclude("resultType")
.byDefault()
.register();
return mapperFactory;
}
Когда я отправляю следующий запрос ...
{
"name": "example-matcher",
"matchOperation": { "eq": [ { "ref": [ "jdbc-resource", "login" ] }, { "ref": [ "ad-resource", "userPrincipalName" ] } ] }
}
... моя десериализация из JSON в MatcherDTO выглядит так, как я ожидаю:
this = {MatcherDTO}
+- name = {String} "example-matcher"
+- matchExpression = {Equals}
+- inputs = {ArrayList}
+- 0 = {AttributeReference}
+- inputs = {ArrayList}
+- 0 = {String} "jdbc-resource"
+- 1 = {String} "login"
+- 1 = {AttributeReference}
+- inputs = {ArrayList}
+- 0 = {String} "ad-resource"
+- 1 = {String} "userPrincipalName"
Однако, когда я передаю свой MatcherDTO через маппер для создания Matcher, первый уровень рекурсии Операции корректен (а именно, экземпляр Equals), но вторые уровни неверны (а именно, экземпляры Object вместо экземпляров AttributeReference):
this = {Matcher}
+- name = {String} "example-matcher"
+- match = {Equals}
+- inputs = {ArrayList}
+- 0 = {Object}
+- 1 = {Object}
Теперь я могу обойти это путем явного отображения подклассов Operation, например, так:
@Bean
public MapperFactory mapperFactory() {
final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Matcher.class, MatcherDTO.class)
.field("match", "matchExpression")
.byDefault()
.register();
mapperFactory.classMap(Operation.class, Operation.class)
.exclude("resultType")
.byDefault()
.register();
mapperFactory.classMap(AttributeReference.class, AttributeReference.class)
.exclude("resultType")
.byDefault()
.register();
mapperFactory.classMap(Equals.class, Equals.class)
.exclude("resultType")
.byDefault()
.register();
// and so on...
return mapperFactory;
}
Однако, похоже, мне не нужно было этого делать. Как видите, первый уровень рекурсии правильно идентифицировал мой подкласс Equals и отобразил его в соответствии с инструкциями отображения класса Operation, но второй уровень по какой-то причине не удался. В настоящее время существует более десятка реализаций Operation, и я бы не стал явно объявлять их сопоставления. Однако если я сделаю это, существует опасность того, что подклассы Operation будут объявлены в зависимости, и если будущие версии этой зависимости приведут к новым реализациям, мне придется реагировать с новой версией моего кода.