Reputation: 2900
I have the following class structure (code at the end):
abstract class AbstractThing
: I have some different kinds of things with common fieldsclass Foo extends AbstractThing
: a foo is one kind of thingabstract class AbstractReq <T extends AbstractThing>
: I have requests that relate to things, where the subject of the request is a field (T subject
)FooReq extends AbstractReq<Foo>
: Requests about foos, specificallyFooReqDto
: a DTO class with all of the fields in a FooReq
, flattenedI'm trying to map an instance of FooReq
to FooReqDto
using ModelMapper. If I don't need to customize my type mappings, everything works great. But I do (in my actual code) need to customize some things, represented by the field called somethingDifferent
instead of subjectFooField
in FooReqDto
. Now when I run my test, I get the following error:
java.lang.ClassCastException: class com.example.test.TempTest$AbstractThing$ByteBuddy$PsTTXfw5 cannot be cast to class com.example.test.TempTest$Foo (com.example.test.TempTest$AbstractThing$ByteBuddy$PsTTXfw5 and com.example.test.TempTest$Foo are in unnamed module of loader 'app')
What am I missing when creating my type map? It doesn't seem like there's any inherent problem with mapping classes that have inheritance & generics involved, since it works when there's no mapping. And the actual mappings I'm using worked fine before I pulled out the abstract classes (which is necessary to cut down on a lot of duplicate code).
Here's the full code:
package com.example.test;
import org.junit.Test;
import org.modelmapper.ModelMapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TempTest {
@Test
public void testStuff() {
final ModelMapper mapper = new ModelMapper();
mapper.createTypeMap(FooReq.class, FooReqDto.class)
.addMappings(map -> {
map.map(req -> req.getSubject().getFooField(), FooReqDto::setSomethingDifferent);
});
final FooReq req = new FooReq();
req.setSubmitted(true);
req.getSubject().setName("testFoo");
req.getSubject().setFooField("bar");
final FooReqDto dto = mapper.map(req, FooReqDto.class);
assertTrue(dto.isSubmitted());
assertEquals("testFoo", dto.getSubjectName());
assertEquals("bar", dto.getSomethingDifferent());
}
public static abstract class AbstractThing {
private String name;
// getters and setters omitted
}
public static class Foo extends AbstractThing {
private String fooField;
// getters and setters omitted
}
public static abstract class AbstractReq <T extends AbstractThing> {
protected T subject;
private boolean submitted;
// getters and setters omitted
}
public static class FooReq extends AbstractReq<Foo> {
public FooReq() {
super();
subject = new Foo();
}
}
public static class FooReqDto {
private boolean submitted;
private String subjectName;
private String somethingDifferent;
public FooReqDto() {
}
// getters and setters omitted
}
}
Upvotes: 0
Views: 122
Reputation: 2900
A quick workaround to this is to add explicit setters to FooReq
:
public static class FooReq extends AbstractReq<Foo> {
public FooReq() {
super();
subject = new Foo();
}
public Foo getSubject() { return subject; }
public void setSubject(Foo subject) { this.subject = subject; }
}
It's redundant with the parent getter and setter, but stops ModelMapper from getting confused when using reflection & losing generic info at runtime.
If the only goal of the parent class here is to reduce duplication in the model classes themselves, this would mostly defeat the purpose. In my case, this setup is a tool so I can accomplish other things, so this solution works for me. I'd be interested to see other options that work by only adjusting the ModelMapper config, however.
Upvotes: 0