Reputation: 3992
Consider the following situation:
public class A {
private String stringA;
public String getStringA() {
return stringA;
}
public void setStringA(String stringA) {
this.stringA = stringA;
}
}
public class B {
List<SomeObject> someObjects;
public List<SomeObject> getSomeObjects() {
if (someObjects == null) {
someObjects = new ArrayList<SomeObject>();
}
return someObjects;
}
}
public class SomeObject {
private String stringSomeObject;
public String getStringSomeObject() {
return stringSomeObject;
}
public void setStringSomeObject(String stringSomeObject) {
this.stringSomeObject = stringSomeObject;
}
}
I want to map from A
to B
. Whilst mapping these, stringA
needs to be mapped to stringSomeObject
in SomeObject
. I tried to write a Orika-Mapper for this:
public class MyMapper extends ConfigurableMapper {
@Override
protected void configure(MapperFactory factory) {
ConverterFactory converterFactory = factory.getConverterFactory();
converterFactory.registerConverter(new StringToSomeObjectConverter());
factory.classMap(A.class, B.class) //
.field("stringA", "someObjects") //
.byDefault() //
.register();
}
}
It maps class A
to B
and whenever it encounters a conversion from String
to List<SomeObject>
it calls a custom-converter:
public class StringToSomeObjectConverter extends CustomConverter<String, List<SomeObject>> {
private static final String BORROWER_PARTY_TYP_CODE = "147";
@Override
public List<SomeObject> convert(String source, Type<? extends List<SomeObject>> destinationType) {
SomeObject someObject = new SomeObject();
someObject.setStringSomeObject(source);
return Arrays.asList(someObject);
}
}
I wrote an unit-test to ensure that this works:
@Test
public void testMap() throws Exception {
A a = new A();
a.setStringA("a");
B outcome = new MyMapper().map(a, B.class);
assertThat(outcome.getSomeObjects.size(), is(1));
}
Sadly this test fails with:
java.lang.AssertionError:
Expected: is <1>
but: was <0>
It seems like the Converter is never executed so I tried to debug it. And indeed: The debugger never reaches the converter. Am I doing something wrong? It seems like. I know there are more methods which one could go with like: mapAToB e.g...
Ok I found a solut...nah! It's not a solution, it's just a workaround. I defined the stringA
as List<String>
as well and defined a converter extending CustomConverter<String, LoanContrReqERPCrteReqLoanContrBrrwrPty>
.
Because this feels a little "hacky", I am still interested in a nice solution. (Though I am just thinking that this solution might be fine: Now the datastructure of both objects is more equal than before. The problem is, that object B
is coming from an external service, I can't modify it.)
Upvotes: 1
Views: 4462
Reputation: 3072
You mapping doesn't work because you don't have setter for someObjects
.
When Orika tries to generate code for mapper, it checks all fieldMaps
in classMap
for sourceProperty
is readable and destinationProperty
is assignable. If this checks passed, generator puts field conversion into generated mapper. If check failed, Orika just skip this field conversion.
Few options you can use to solve a problem:
You can add setter for someObjects
field in class B
:
public static class B {
List<SomeObject> someObjects;
public List<SomeObject> getSomeObjects() {
if (someObjects == null) {
someObjects = new ArrayList<SomeObject>();
}
return someObjects;
}
public void setSomeObjects(List<SomeObject> someObjects) {
this.someObjects = someObjects;
}
}
Use custom mapper instead of converter:
factory.classMap(A.class, B.class)
.customize(
new CustomMapper<A, B>() {
@Override
public void mapAtoB(A a, B b, MappingContext context) {
SomeObject someObject = new SomeObject();
someObject.setStringSomeObject(a.getStringA());
b.getSomeObjects().add(someObject);
}
}
)
.byDefault()
.register();
Orika will put invocation customMapper after resolving of field maps.
Generated mapper will looks like:
b.setOtherField(a.getOtherField());
if (customMapper != null) {
customMapper.map(source, destination); <-- Your mapper invocation
}
Use follow syntax for fields:
factory.classMap(A.class, B.class)
.field("stringA", "someObjects[0].stringSomeObject")
.byDefault()
.register();
Generated mapper will looks like:
if (source.getStringA() != null) {
if (((((java.util.List) destination.getSomeObjects()).size() <= 0 || ((List) destination.getSomeObjects()).get(0) == null))) {
((java.util.List) destination.getSomeObjects()).add(0, ((BoundMapperFacade) usedMapperFacades[0]).newObject(((String) source.getStringA()), mappingContext));
}
}
if (!(((java.lang.String) source.getStringA()) == null)) {
(((java.util.List) destination.getSomeObjects()).get(0)).setStringSomeObject(source.getStringA());
} else if (!(((java.util.List) destination.getSomeObjects()) == null) && !((((java.util.List) destination.getSomeObjects()).size() <= 0 || ((List) destination.getSomeObjects()).get(0) == null))) {
( ((java.util.List) destination.getSomeObjects()).get(0)).setStringSomeObject(null);
}
Also there was a bug in Orika to map from single property to property of collection using syntax .field("stringA", "elements{stringB}")
( Incorrect mapper code generated for mapping from a single property to property of collection element). Bug closed at 31 Dec 2016 here: Fix for bug
Upvotes: 2