Use case
We have a lot of technical mapping like date, enum (domain <-> Dto), etc.
I would like to write only the not null version add have mapstruct generating the boiling code to deal with the Nullable version when needed.
Exa
Generated Code
Mapper definition:
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import static tech.lightframe.shift.shared.TimeZoneConstants.UTC_ZONE_ID;
@Mapper
@AnnotateWith(NullMarked.class)
@NullMarked
public interface SimpleMapper {
record SourceEntity(ZonedDateTime createdAt, @Nullable ZonedDateTime closedAt) {
}
record TargetDto(OffsetDateTime createdAt, @Nullable OffsetDateTime closedAt) {
}
// Only define the mapping once for non null
default OffsetDateTime toOffsetTime(ZonedDateTime zt) {
return zt.withZoneSameInstant(UTC_ZONE_ID).toOffsetDateTime();
}
/// The mapping method should be able to map it
TargetDto toDto(SourceEntity sourceEntity);
}
What is generated now:
@NullMarked
@Component
public class SimpleMapperImpl implements SimpleMapper {
@Override
public SimpleMapper.TargetDto toDto(SimpleMapper.SourceEntity sourceEntity) {
OffsetDateTime createdAt = null;
OffsetDateTime closedAt = null;
createdAt = toOffsetTime( sourceEntity.createdAt() );
closedAt = toOffsetTime( sourceEntity.closedAt() ); <= Invalid code, it silently pass a null value to nonnull method
SimpleMapper.TargetDto targetDto = new SimpleMapper.TargetDto( createdAt, closedAt );
return targetDto;
}
}
Expected:
@NullMarked
@Component
public class SimpleMapperImpl implements SimpleMapper {
@Override
public SimpleMapper.TargetDto toDto(SimpleMapper.SourceEntity sourceEntity) {
OffsetDateTime createdAt = null;
OffsetDateTime closedAt = null;
createdAt = toOffsetTime( sourceEntity.createdAt() );
if (sourceEntity.closedAt() != null){
closedAt = toOffsetTime( sourceEntity.closedAt() );
}
SimpleMapper.TargetDto targetDto = new SimpleMapper.TargetDto( createdAt, closedAt );
return targetDto;
}
}
It's not possible to define 2 mapping methods like that:
// Only define the mapping once for non null
default OffsetDateTime toOffsetTime(ZonedDateTime zt) {
return zt.withZoneSameInstant(UTC_ZONE_ID).toOffsetDateTime();
}
default @Nullable OffsetDateTime toOffsetTimeNull(@Nullable ZonedDateTime zt) {
if (zt == null) {
return null;
}
return zt.withZoneSameInstant(UTC_ZONE_ID).toOffsetDateTime();
}
With this configuration MapStruct fails with the error:
Ambiguous mapping methods found for mapping property "ZonedDateTime createdAt" to OffsetDateTime: OffsetDateTime toOffsetTime(ZonedDateTime zt), OffsetDateTime toOffsetTimeNull(ZonedDateTime zt). See https://mapstruct.org/faq/#ambiguous for more info.
Possible workarounds
Use a qulifier with 2 different mapping.
MapStruct Version
1.7.0.Beta2
Use case
We have a lot of technical mapping like date, enum (domain <-> Dto), etc.
I would like to write only the not null version add have mapstruct generating the boiling code to deal with the Nullable version when needed.
Exa
Generated Code
Mapper definition:
What is generated now:
Expected:
It's not possible to define 2 mapping methods like that:
With this configuration MapStruct fails with the error:
Possible workarounds
Use a qulifier with 2 different mapping.
MapStruct Version
1.7.0.Beta2