Strange behaviour when mapping shared object instance multiple times with inheritance
Opened this issue · 1 comments
There's a strange behaviour while mapping an object instance which was extracted from a list into different objects (with inheritance). In some cases the fields are mapped correctly. This was tested with the lastest Orika version (1.5.4).
Bug1:
Output:
OrikaTest.DestContainer(a=OrikaTest.A(f1=null), b=[OrikaTest.B(super=OrikaTest.A(f1=foo), f2=bar), OrikaTest.B(super=OrikaTest.A(f1=hello), f2=world)])
Bug2:
Output:
OrikaTest.DestContainer(a=OrikaTest.A(f1=foo), b=[OrikaTest.B(super=OrikaTest.A(f1=null), f2=null), OrikaTest.B(super=OrikaTest.A(f1=hello), f2=null)])
Expected:
OrikaTest.DestContainer(a=OrikaTest.A(f1=foo), b=[OrikaTest.B(super=OrikaTest.A(f1=foo), f2=bar), OrikaTest.B(super=OrikaTest.A(f1=hello), f2=world)])
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.ConfigurableMapper;
import ma.glasnost.orika.impl.DefaultMapperFactory;
public class OrikaTest {
@Data
@AllArgsConstructor
public static class Container {
private List<Source> source;
public Source getFirst() {
return source.get(0);
}
}
@Data
@AllArgsConstructor
public static class Source {
private SourceAttributes attributes;
}
@Data
@AllArgsConstructor
public static class SourceAttributes {
private String f1;
private String f2;
}
@Data
public static class DestContainer {
private A a;
private List<B> b = new ArrayList<>();
}
@Data
public static class A {
private String f1;
}
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
public static class B extends A {
private String f2;
}
public static class ExampleMapper extends ConfigurableMapper {
private final int bug;
public ExampleMapper(int bug) {
super(false);
this.bug = bug;
init();
}
@Override
protected void configureFactoryBuilder(DefaultMapperFactory.Builder factoryBuilder) {
factoryBuilder.useAutoMapping(false);
}
@Override
protected void configure(MapperFactory factory) {
if (bug == 1) {
factory.classMap(Container.class, DestContainer.class)
// switching these fields causes
// OrikaTest.DestContainer(a=OrikaTest.A(f1=null), b=[OrikaTest.B(super=OrikaTest.A(f1=foo), f2=bar), OrikaTest.B(super=OrikaTest.A(f1=hello), f2=world)])
.field("source", "b")
.field("first", "a")
.register();
} else {
factory.classMap(Container.class, DestContainer.class)
.field("first", "a")
.field("source", "b")
.register();
}
factory.classMap(Source.class, A.class)
.field("attributes", "")
.register();
if (bug != 2) {
// removing this causes
// OrikaTest.DestContainer(a=OrikaTest.A(f1=foo), b=[OrikaTest.B(super=OrikaTest.A(f1=null), f2=null), OrikaTest.B(super=OrikaTest.A(f1=hello), f2=null)])
factory.classMap(Source.class, B.class)
.field("attributes", "")
.register();
}
factory.classMap(SourceAttributes.class, A.class)
.byDefault()
.register();
factory.classMap(SourceAttributes.class, B.class)
.byDefault()
.register();
}
}
private Container container = new Container(Arrays.asList(new Source(new SourceAttributes("foo", "bar")), new Source(new SourceAttributes("hello", "world"))));
@Test
public void testOkMapping() {
ExampleMapper mapper = new ExampleMapper(0);
DestContainer destContainer = mapper.map(container, DestContainer.class);
doAssertions(destContainer);
}
@Test
public void testBug1() {
ExampleMapper mapper = new ExampleMapper(1);
DestContainer destContainer = mapper.map(container, DestContainer.class);
doAssertions(destContainer);
}
@Test
public void testBug2() {
ExampleMapper mapper = new ExampleMapper(2);
DestContainer destContainer = mapper.map(container, DestContainer.class);
doAssertions(destContainer);
}
private void doAssertions(DestContainer destContainer) {
System.out.println(destContainer);
Assert.assertNotNull(destContainer.getA());
Assert.assertNotNull(destContainer.getA().getF1());
Assert.assertNotNull(destContainer.getB());
Assert.assertNotNull(destContainer.getB().get(0).getF1());
Assert.assertNotNull(destContainer.getB().get(0).getF2());
Assert.assertNotNull(destContainer.getB().get(1).getF1());
Assert.assertNotNull(destContainer.getB().get(1).getF2());
}
}
A possible solution is to use the BoundMapperFacade with the "containsCycle=false" option.