Polymorphic Mapping Fails in Recursion
chaserb opened this issue · 2 comments
Using orika-core 1.5.4 and Java 8, cross posted on stackoverflow
Given the following classes (getters, setters, and abstract implementations omitted for brevity) ...
public class Matcher {
privat 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 {
...
}
...
...I put together the following 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;
}
When I POST the following request...
{
"name": "example-matcher",
"matchOperation": { "eq": [ { "ref": [ "jdbc-resource", "login" ] }, { "ref": [ "ad-resource", "userPrincipalName" ] } ] }
}
...my deserialization from JSON to MatcherDTO looks like I expect:
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"
However, when I pass my MatcherDTO through the mapper to produce a Matcher, the first level of Operation recursion is correct (namely, an Equals instance), but the second levels are incorrect (namely, Object instances instead of AttributeReference instances):
this = {Matcher}
+- name = {String} "example-matcher"
+- match = {Equals}
+- inputs = {ArrayList}
+- 0 = {Object}
+- 1 = {Object}
Now, I am able to work around this by explicitly mapping Operation subclasses like so:
@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;
}
However, it seems like I should not have to do this. As you can see, the first level of recursion correctly identified my Equals subclass and mapped it according to the Operation class mapping instructions, but the second level failed for some reason. There are currently a dozen plus Operation implementations, and I'd rather not have to explicitly declare their mappings. If I do this, though, the danger is that the Operation subclasses are declared in a dependency, and if future versions of that dependency produce new implementations, I'll have to react with a new version of my code.
I've found a workaround which will work for my use case, and that's to declare a pass through converter which simply passes all Operation objects from source to destination by reference:
mapperFactory.getConverterFactory()
.registerConverter(new PassThroughConverter(Operation.class));
I'm going to apply this as a workaround, since it's acceptable for my source and destination to have references to the same Operation objects. If someone has an answer for a true deep copy, however, I would be interested to know it.
In my humble opinion the domain model is not properly designed. It's intended to be generic but you don't leverage Java generics. You need to give more hints to orika in order to map objects correctly. However PassThroughConverter is a good option for your domain model.