Conversion between two classes

linpeilie

Simple Conversion

To convert between two classes, simply add the annotation @AutoMapper to one of the classes, configure the target attribute, and specify the target class.

eg:

@AutoMapper(target = CarDto.class)
public class Car {
    // ...
}

This example shows that an interface CarToCarDtoMapper and implementation class CarToCarDtoMapperImpl will be generated for Car to CarDto. In the generated conversion code, all readable attributes of the source type(Car) are copied to the corresponding attribute of the target attribute type(CarDto).

When an attribute has the same name as its target entity counterpart, it is implicitly mapped.

In addition, MapStructPlus generates the CarDto to Car interface CarDtoToCarMapper and the implementation class CarDtoToCarMapperImpl according to the current default rules. If you do not want to generate the transformation logic, you can configure it by using the reverseConvertGenerate property of the annotation.

The properties of a custom object are automatically converted

When property by custom class exists in the class to be converted, the conversion method for that type is automatically found.

For example, there are two sets of object module: Car and SeatConfiguration, Car depends on SeatConfiguration

The corresponding objects are as follows:

  • car
@AutoMapper(target = CarDto.class)
@Data
public class Car {
    private SeatConfiguration seatConfiguration;
}
@Data
public class CarDto {
    private SeatConfigurationDto seatConfiguration;
}
  • seat configuration
@Data
@AutoMapper(target = SeatConfigurationDto.class)
public class SeatConfiguration {
    // fields
} 
@Data
public class SeatConfigurationDto {
    // fields
} 

In the above example, the CarToCarDtoMapper and SeatConfigurationToSeatConfigurationDtoMapper conversion interfaces are generated, and SeatConfigurationToSeatConfigurationDtoMapper is automatically used to convert the seat properties in the Car conversion.

Introduces custom type converter

When different types of properties want to be converted according to custom rules, there are two ways:

  1. Configuration through the expression configured in @AutoMapping
  2. Customize a type converter, introduced through the 'uses' attribute of @AutoMapping

For mode one, refer to the sectionexpresions below.

This is based on mode two, where the implementation converts a String type attribute, separated by commas, to a List<String> type attribute:

First, define a type converter --- StringToListString:

@Component
public class StringToListString {
    public List<String> stringToListString(String str) {
        return StrUtil.split(str);
    }
}

WARNING

  • Type converter provides type conversion methods thas can be defined as static or nonstatic.
  • If you are using the framework based on the SpringBoot approach, the type converter need to be defined as a Spring Bean.

Next, use is:

@AutoMapper(target = User.class, uses = StringToListStringConverter.class)
public class UserDto {

    private String username;
    private int age;
    private boolean young;
    @AutoMapping(target = "educationList")
    private String educations;
    // ......
}

Test:

@SpringBootTest
public class QuickStartTest {

    @Autowired
    private Converter converter;

    @Test
    public void ueseTest() {
        UserDto userDto = new UserDto();
        userDto.setEducations("1,2,3");

        final User user = converter.convert(userDto, User.class);
        System.out.println(user.getEducationList());    // [1, 2, 3]

        assert user.getEducationList().size() == 3;
    }
}

When there are multiple methods in a custom type converter, you can also specify the concrete conversion method with @AutoMapping 's qualifiedByName. You can refer to specify conversion methods, sections.

Custom Property conversions

When there are inconsistent scenarios for attributes in the two classes, such as name, type, and so on, you can used to configure the mapping rules by add @AutoMapping to the attributes.

Different attribute name mappings

In the @AutoMapping annotation, the target attribute is provided to configure the mapping between the current attribute and the target attribute in the target class.

For example, when Car is converted to CarDto, the seatConfiguration attribute corresponds to the seat attribute:

@AutoMapper(target = CarDto.class)
@Data
public class Car {
    @AutoMapping(target = "seat")
    private SeatConfiguration seatConfiguration;
}

The @AutoMapping annotation also provides the source attribute, which by default takes the name of the current property and can be configured to fit a scenario there a property of the current class, its internal property, to a property field in the target, you can configure it with the current property.

eg:

@Data
@AutoMapper(target = GoodsVo.class, reverseConvertGenerate = false)
public class Goods {

    @AutoMapping(source = "sku.price", target = "price")
    private Sku sku;

}
@Data
public class GoodsVo {

    private Integer price;

}

Specifies the time format

When the time type(for example DateLocalDateTimeLocalDate...) needs to be converted with String by specifying the time format, you can configure it with dateFormat in @AutoMapping

eg:

@Data
@AutoMapper(target = OrderEntity.class)
public class Order {

    @AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime orderTime;

    @AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
    private Date createTime;

    @AutoMapping(target = "orderDate", dateFormat = "yyyy-MM-dd")
    private String date;

}
@Data
@AutoMapper(target = Order.class)
public class OrderEntity {

    @AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
    private String orderTime;

    @AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
    private String createTime;

    @AutoMapping(target = "date", dateFormat = "yyyy-MM-dd")
    private LocalDate orderDate;

}

Specifies a numeric format

When the conversion between a numeric type(for example int/Integer and the wrapper class、BigDecimal) and String requires a numeric format, it can be configured with numberFormat in @AutoMapping

This format need to be supported by java.text.DecimalFormat

eg:

@Data
@AutoMapper(target = OrderEntity.class)
public class Order {

    @AutoMapping(numberFormat = "$0.00")
    private BigDecimal orderPrice;

    @AutoMapping(numberFormat = "$0.00")
    private Integer goodsNum;
    
}
@Data
@AutoMapper(target = Order.class)
public class OrderEntity {

    @AutoMapping(numberFormat = "$0.00")
    private String orderPrice;

    @AutoMapping(numberFormat = "$0.00")
    private String goodsNum;
    
}

Ignore the specifies property

When the transformation is performed, the transformation of the specified property needs to be ignored, which can be configured using the ignore of @AutoMapping

es:

@AutoMapper(target = CarDto.class)
@Data
public class Car {

    @AutoMapping(target = "wheels", ignore = true)
    private Wheels wheels;
    
} 

default value

defaultValue in @AutoMapping can specify the default value to convert to the target class when the property value is null.

eg:

@Data
@AutoMapper(target = DefaultVo.class)
public class DefaultDto {

    @AutoMapping(defaultValue = "18")
    private Integer i;

    @AutoMapping(defaultValue = "1.32")
    private Double d;

    @AutoMapping(defaultValue = "true")
    private Boolean b;

}

expression

When you perform a property conversion, you can perform the conversion operation by specifying that a piece of Java code be executed, for example, to return after converting a property in a source object.

Note that at compile time, the expression is inserted directly into the transformation logic and its synatax is not validated.

For example, the List<String> attribute in the source object is concatenated into a string by ',':

@AutoMapper(target = UserDto.class)
public class User {

    @AutoMapping(target = "educations", expression = "java(java.lang.String.join(\",\", source.getEducationList()))")
    private List<String> educationList;

}

specify conversion methods

INFO

since 1.4.0

Note that this feature needs to be used in conjunction with @AutoMapper 's uses.

When an attribute needs to define the transformation logic separately and is complex, you can implement the method first, specifying it by qualifiedByName.

For example:

Need film distribution, need according to the different language, change to the corresponding language title. There are two conversion methods, one is to convert English to French, and the other is to convert French to English:

@Component
@Named("TitleTranslator")
public class Titles {

    @Named("EnglishToFrench")
    public String translateTitleEF(String title) {
        if ("One Hundred Years of Solitude".equals(title)) {
            return "Cent ans de solitude";
        }
        return "Inconnu et inconnu";
    }

    @Named("FrenchToEnglish")
    public String translateTitleFE(String title) {
        if ("Cent ans de solitude".equals(title)) {
            return "One Hundred Years of Solitude";
        }
        return "Unknown";
    }

}

The conversion logic is then applied:

@Data
@AutoMapper(target = FrenchRelease.class, uses = Titles.class)
public class EnglishRelease {

    @AutoMapping(qualifiedByName = "EnglishToFrench")
    private String title;

}
@Data
@AutoMapper(target = EnglishRelease.class, uses = Titles.class)
public class FrenchRelease {

    @AutoMapping(qualifiedByName = "FrenchToEnglish")
    private String title;

}

Specifies a dependency between fields

When the transformation logic for attribute A, which depends on attribute B, can specify that attribute a depends on B, the transformation will first transform B and then transform A.

Example:

@Data
@AutoMapper(target = DependsTarget.class)
public class DependsSource {

    private String firstName;
    private String lastName;
    @AutoMapping(dependsOn = {"firstName", "lastName"})
    private String fullName;

}
@Data
public class DependsTarget {

    private String firstName;
    private String lastName;
    private String fullName;

}

Automatically access the custom converter interface

INFO

since 1.2.3

When some type conversion logic is more complex, you can use define converter interface to achive, that is, using MapStruct native way.

When used this way, the default generated type conversion is automatically referenced if there is previously provided type conversion.

例如:

@AutoMapper(target = CarDto.class)
@Data
public class Car {
    private Tyre tyre;
}
@Data
public class CarDto {
    private TyreDTO tyre;
}

Converter interface between Tyre and TyreDTO is defined here.

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface TyreMapper {

    TyreDTO tyreToTyreDTO(Tyre tyre);

    Tyre tyreDtoToTyre(TyreDTO tyreDTO);

}

The generated implementation classes for the Car and CarDto converter interfaces are as follows:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-04-24T15:38:48+0800",
    comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@Component
public class CarToCarDtoMapperImpl implements CarToCarDtoMapper {

    @Autowired
    private TyreMapper tyreMapper;

    @Override
    public CarDto convert(Car source) {
        if ( source == null ) {
            return null;
        }

        CarDto carDto = new CarDto();

        carDto.setTyre( tyreMapper.tyreToTyreDTO( source.getTyre() ) );

        return carDto;
    }

    @Override
    public CarDto convert(Car source, CarDto target) {
        if ( source == null ) {
            return target;
        }

        target.setTyre( tyreMapper.tyreToTyreDTO( source.getTyre() ) );

        return target;
    }
}
@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-04-24T15:38:49+0800",
    comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@Component
public class CarDtoToCarMapperImpl implements CarDtoToCarMapper {

    @Autowired
    private TyreMapper tyreMapper;

    @Override
    public Car convert(CarDto source) {
        if ( source == null ) {
            return null;
        }

        Car car = new Car();

        car.setTyre( tyreMapper.tyreDtoToTyre( source.getTyre() ) );

        return car;
    }

    @Override
    public Car convert(CarDto source, Car target) {
        if ( source == null ) {
            return target;
        }

        target.setTyre( tyreMapper.tyreDtoToTyre( source.getTyre() ) );

        return target;
    }
}

Configuration for reverse property mapping

INFO

In this article, all the mentioned source classes refer to classes annotated by @AutoMapper; the target classes refer to the type specified by the target attribute in @AutoMapper.

As mentioned earlier, when you add the @AutoMapper annotation to a class, by default, generate not only the source-to-target converter interface, but also the target-to-source converter interface and implementation class, note here that the converter interface generated by default does not any custom configuration, even if the @AutoMapping annotation is configured in the source class.

There are two ways to implement a custom converter configuration from the target class to the source class:

  1. Add the @AutoMapper annotation above the target class. This is the most recommended approach, when both sides add this annotation, the default converter interface for target-to-source is not generated, that is, it is generated according to custom rules.
  2. When the target class does not have access to the source class, or the project specification does not allow such annotations to be added to the target class, you can add the custom configuration entirelyto the source class. This is the reverse attribute mapping configuration described below.

The @ReverseAutoMapping annotation is provided in the framework to configure custom conversion rules from the target class to the source class.

WARNING

Note here that to prevent configuration conflicts, once you add the @ReverseAutoMapping annotation, you can not add any custom conversion annotations to the target class.

The meaning of the @ReverseAutoMapping annotation is that when the target class is converted to the source class, the custom rules need to be specified, where the attributes can be configured, consistent with the @AutoMapping annotation.

There are two attributes to note here, source and target.

Here the source refers to the attributes in the target class, and the target refers to the attributes in the source class.

One might wonder why the configuration here seems to be reversed? If not, you can skip it.

When the framework is designed, all the attribute transformation configurations are based on the type to be converted, with the ultimate effect of converting the class to the target class. So The source here should also be an attribute in the source class.

If you sill don't understand it, you can assume that the annotation is the @AutoMapping annotation that should have been applied to the target class. Just copy it to the current class and change the annotation name.

Immutable type

since 1.3.2

When source type is immutable, the T convert(S source, @MappingTarget T target) method generated by the previous default rule may have problems.

So, you can use the Immutable annotation under any package to identify a class as an Immutable type, When an immutable type is used, the @MappingTarget makes no sense, and the above method eventually generates the following:

public T convert(S source, @MappingTarget T target) {
    return target;
}
Last Updated 3/25/2024, 4:00:35 AM