javaspring-bootmapstruct

Mapstruct - String manipulation, but only on one property


I'm not sure what I'm missing here. My custom logic applies to all String attributes, instead of just one, that I specified for target.

@ApiModel(value="ShopProduct")
@Data
public class ShopProductDTO {
    private String description;
    private String fullImagePath;
}

@Entity
@Table(name = "shop_product")
public class ShopProduct implements Serializable {
    @Column(name = "desc")
    private String description;
    @Column(name = "thumb_path")
    private String thumbPath;
//getters,setters

ImageMapper:

@Component
public class ImagePathMapper {

    AppConfig appConfig;

    public ImagePathMapper(AppConfig appConfig) {
        this.appConfig = appConfig;
    }

    public String toFullImagePath(String thumbPath){
        return appConfig.getImagePath() + thumbPath;
    }
}

ShopProduct Mapper:

@Mapper(componentModel = "spring", uses = {ImagePathMapper.class})
@Component
public interface ShopProductMapper {
    @Mapping(target = "fullImagePath", source = "thumbPath")
    ShopProductDTO shopProductToShopProductDTO(ShopProduct shopProduct);

}

The generated mapstruct class:

        ShopProductDTO shopProductDTO = new ShopProductDTO();

        shopProductDTO.setDescription( imagePathMapper.toFullImagePath( shopProduct.getName() ) );

        shopProductDTO.setFullImagePath( imagePathMapper.toFullImagePath( shopProduct.getThumbPath() ) );

}

Why is the description field also being used with toFullImagePath?

Shouldn't this @Mapping(target = "fullImagePath", source = "thumbPath") specify that I want fullImagePath changed only?


Solution

  • This is not how it works.

    When you define a mapping, Mapstruct will try to map every property in the source object to the target object based mainly on property name conventions.

    When you define a @Mapping annotation you are indicating exceptions to this basic mapping.

    In your example, when you define this:

    @Mapping(target = "fullImagePath", source = "thumbPath")
    

    You are indicating that the property thumbPath in the source object should be mapped to the fullImagePath in the destination object: it is necessary because they have different names and otherwise the map will not succeed.

    On the other hand, when you define the uses=ImagePathMapper.class attribute you are instructing Mapstruct to convert every property defined as a certain Class type, String in this case, found in the source object: as long as all the fields defined in ShopProduct are Strings this mapper will be applied for every property.

    If you want to apply the mapper just for one field you can invoke a custom mapping method, or use decorators, or the before and after mapping annotations, like in the following example:

    @Mapper(componentModel = "spring", uses=AppConfig.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
    public abstract class ShopProductMapper {
    
      abstract ShopProductDTO shopProductToShopProductDTO(ShopProduct shopProduct);
    
      @Autowired
      public void setAppConfig(AppConfig appConfig) {
        this.appConfig = appConfig;
      }
    
      @AfterMapping
      public void toFullImagePath(@MappingTarget ShopProductDTO shopProductDTO, ShopProduct shopProduct) {
        String thumbPath = shopProduct.getThumbPath();
        if (thumbPath != null) {
          shopProductDTO.setFullImagePath(appConfig.getImagePath() + thumbPath);
        }
      }
    }
    

    Please note in the example that you need to do several changes to your mapper in order to work properly with Spring: see this and this.