Default intermediate instances for "multi dot delimited" paths? #3669
Replies: 7 comments · 6 replies
|
Can you please provide the mapper that have as well? The problem here is that MapStruct has no way of knowing that You might be able to solve it by changing your mapper, but would need to see your existing mapping to be able to help more there |
|
Yes, that is the problem. And a pretty general situation. I'd use whatever mapper configuration solves the problem. But I currently use an interface for the Mapping and Mapstruct instantiation: @Mapper(
config = CommonMappingConfig.class,
builder = @Builder(disableBuilder = true),
uses = { CommonMapping.class, DateMapping.class, DimensionedUnitsMapping.class, EnumMapping.class })
public interface LocationMapping {
LocationMapping INSTANCE = newMapper();
static LocationMapping newMapper() {
LocationMapping mapper = Mappers.getMapper(LocationMapping.class);
return mapper;
}
@Mapping(target = "currentLocation.time", source = "createdAt", qualifiedBy = DateTimeGroup.class)
@Mapping(target = "currentLocation.lat", source = "latitude")
@Mapping(target = "currentLocation.lng", source = "longitude")
void fromPojo(PojoLocation pojo, @MappingTarget ModelWithLocation model, @Context LocationMapping_Ctx ctx);
} |
@AfterMapping
default void fromPojo_currentLocation(PojoLocation pojo, @MappingTarget ModelWithLocation model, @Context LocationMapping_Ctx ctx){
if (pojo.createdAt == null && pojo.lat == null && pojo.lng == null){
return;
}
GeoLocation loc = model.getCurrentLocation();
if (loc == null){
loc = new PointLocation(); //can use the subclass because I know the context of the code. Could also delegate to the ctx instance.
model.setCurrentLocation(loc);
}
loc.time = pojo.createdAt;
loc.lat = pojo.lat;
loc.lng = pojo.lng;
} |
class ModelWithLocation{
public GeoLocation getGeoLocationEnsured(){
if (currentLocation == null){
currentLocation = newCurrentLocation(); //internal method. May not know the current context, though
}
return currentLocation;
}
///make the java bean specification happy with a matching setter
public void setGeoLocationEnsured(GeoLocation loc){
setGeoLocation(loc);
}
}And the mapping becomes: Note that this will still create a compile error in I could write a |
Still have the compile error in the LocationMappingImpl class but it will never execute. Disadvantage is that we have to instantiate a PointLocation even if all the pojo fields are null. If that is a real concern then add an AfterMapping method to check for nulls and remove the PointLocation if it has null field values. @BeforeMapping
default void fromPojo_location(PojoLocation pojo, @MappingTarget ModelWithLocation model, @Context LocationMapping_Ctx ctx) {
GeoLocation geo = model.getCurrentLocation();
if (geo == null) {
geo = ctx.newGeoLocation(pojo, model); //let the context decide which subclass to instantiate
model.setCurrentLocation(geo);
}
} |
Add a field to @Mapping(target = "currentLocation.time", source = "createdAt", qualifiedBy = DateTimeGroup.class, pathPopulateBy= CurrentLocationPopulateAnnot.class)
@Mapping(target = "currentLocation.lat", source = "latitude", pathPopulateBy= CurrentLocationPopulateAnnot.class)
@Mapping(target = "currentLocation.lng", source = "longitude", pathPopulateBy= CurrentLocationPopulateAnnot.class)
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface CurrentLocationPopulateAnnot {}
@CurrentLocationPopulateAnnot
default void currentLocationPopulate(PojoLocation pojo, @MappingTarget ModelWithLocation model, @Context LocationMapping_Ctx ctx){
if (pojo.createdAt == null && pojo.lat == null && pojo.lng == null){
//Aside: This is wishful thinking because even though I use a @Condition method to check for nulls in my standard mapping, will Mapstruct use it for each step along the path?
return;
}
GeoLocation geo = model.getCurrentLocation();
if (geo == null){
geo = ctx.newGeoLocation(pojo, model); //let the context decide which subclass to instantiate
model.setCurrentLocation(geo);
}
} |
Use a method convention. Add the prefix "new" to the name of the path and write a new method in the LocationMapper interface. MapStruct calls this instead of instantiating GeoLocation itself. GeoLocation newCurrentLocation(PojoLocation pojo, @MappingTarget ModelWithLocation model, @Context LocationMapping_Ctx ctx){
return ctx.newGeoLocation(pojo, model); //let the context decide which subclass to instantiate
} |
Uh oh!
There was an error while loading. Please reload this page.
I am trying to map a source value to a target that "walks" multiple instances. For example, say I am receiving a latitude/longitude from a source and wish to map it to the path
myInstance.currentLocation.latitude. ThecurrentLocationis an intermediate object that may already exist but will have to be instantiated if not.So the java domain classes are:
Mapstruct maps this as follows (roughly; I am simplifying the actual code):
The fatal problem is that it is trying to instantiate the abstract class
GeoLocationinstead of the correct classPointLocation.I know how to solve this by writing my own custom code mapping method, but I wonder if there is a convenient way in mapstruct to control the instantiation of these "intermediate" instances? I cannot find anything in the manual.
All reactions