How can only specified fields be mapped using MapStruct? - java

MapStruct is mapping all the properties of source and destination by default if they have same name. The ignore element in #Mapping can be used for omitting any field mapping. But that's not I want. I want control over the mapping strategy. I want to specify something like:
#Mapper(STRATEGY=MAPPING_STRATEGY.SPECIFIED)
public interface EmployeeToEmployeeDTOMapper {
#Mappings({
#Mapping(target="id", source="id"),
#Mapping(target="name", source="name")
})
public EmployeeDTO employeeToEmployeeDTO (Employee emp);
}
Now this mapping is only meant to map id and name from source to destination. No other fields should be mapped unless specified in the mappings annotation.

As of MapStruct 1.3, the #BeanMapping(ignoreByDefault = true) annotation can be added to the mapping method to achieve this result:
public interface EmployeeToEmployeeDTOMapper {
#BeanMapping(ignoreByDefault = true)
#Mapping(target="id", source="id")
#Mapping(target="name", source="name")
EmployeeDTO employeeToEmployeeDTO(Employee emp);
}
Per the Javadocs of the ignoreByDefault annotation element:
Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No warning will be issued on missing target properties.

What you are looking for is a feature request in #1392. There is a pending PR so it would be available to use in the next version (1.3.0). The final API is not yet defined. Follow the issue and the PR to be notified when it gets done

Related

How to configure MapStruct to throw an exception when enum values can't be mapped

This is my Mapper:
#Mapper
public interface ProductMapper {
ProductClassification toProductClassification(ProductTypes pisType);
}
Where ProductTypes and ProductClassification are enums. I want it to throw an exception when it can't map the enums, but I get compiler error:
The following constants from the source enum have no corresponding constant in the target enum and must be be mapped via adding additional mappings: EXTERNAL, UNKNOWN.
I tried with #ValueMappings annotation, but can only configure it to set the value to null, which is not sufficient:
#ValueMappings({
#ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL)
})
What would be the right way to configure MapStruct mapper to throw an exception when it can't map enum constants?
This is currently not possible.
However, this feature will be part of the next 1.5 release. It is already implemented and available in the SNAPSHOT builds.
There will be a new mapping constant accessible via MappingConstants.THROW_EXCEPTION and you can set it to a ValueMappingTarget.
So in this example in order to throw an exception for any remaining mapping you can write:
#ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.THROW_EXCEPTION)
Note at the time of answering this question (29.03.2021) this feature is not yet released.

Is there any way to avoid using raw string usage in java map-struct?

I want to avoid the human mistake in mapping objects together so I use the map-struct package. but there are situations I should manually assign fields like renaming them. like this method
#Mapper(componentModel = "spring")
public interface ItemMapper extends EntityMapper<ItemDTO, Item> {
#Mapping(target = "itemTeplate", source = "template")
Item toEntity(Entity entity);
}
Is there any way to generate the class field names dynamically for this usage and get an error when changing the naming and have an autocomplete class like fields? like below picture
#Mapper(componentModel = "spring")
public interface ItemMapper extends EntityMapper<ItemDTO, Item> {
#Mapping(target = EntityFields.ITEM_TEMPLATE, source = ItemFields.TEMPLATE)
Item toEntity(Entity entity);
}
MapStruct is an annotation processor and code generator which internals work based on the reflection. The reflection-based look-up for the fields and getters/setters is based on the String matching.
I fully understand your worries, however, consider these facts:
Replacing such string literal with enumeration doesn't help you much and adds only an additional layer. As long as the object field is changed, the enumeration has to be changed as well. To be the devil's advocate, I admit you might want to use it anyway in case of a lot of similar mappings - however, for that I remind you that the mappings can be inherited.
On compilation, MapStruct correctly throws a warning when a field is unmapped, which might happen when a new field is added. If the field is not found at all, i.e. the field is either modified or removed, the compilation fails. MapStruct follows the fail-fast principle.

Configuring JaVers ID for third party objects

I'm trying to use JaVers to store objects from a 3rd-party library that I can't change. The object definition looks something like:
interface TheirObject extends WithId{
//Other properties here...
}
interface WithId{
String getId();
}
with various implementations of that interface. The ID field is not annotated in anyway.
I tried using the default JaVers configuration and got the following error:
JaversException MANAGED_CLASS_MAPPING_ERROR: given javaClass 'interface TheirObject' is mapped to ValueObjectType, expected EntityType
So I configured JaVers as follows:
javers = JaversBuilder.javers()
.registerEntity(new EntityDefinition(TheirObject.class))
.build();
Which gives the exception:
JaversException ENTITY_WITHOUT_ID: Class 'TheirObject' mapped as Entity has no Id property. Use #Id annotation to mark unique and not-null Entity identifier
So I tried telling it about the id field:
javers = JaversBuilder.javers()
.withMappingStyle(MappingStyle.BEAN)
.registerEntity(new EntityDefinition(TheirObject.class, "id"))
.build();
Which gives the exception:
JaversException PROPERTY_NOT_FOUND: Property 'id' not found in class 'TheirObject'. If the name is correct - check annotations. Properties with #DiffIgnore or #Transient are not visible for JaVers.
I've tried a few different variations of id (e.g. Id, getId) but nothing seems to work and I haven't found the documentation very useful in working out how to proceed.
Could someone please help me configure JaVers properly so I can use it to track changes to these objects? Thanks.
Update: I've changed the interface above to better reflect the issue, as I'd over-simplified it to a point where my examples did actually work.
JaversBuilder.javers()
.registerEntity(new EntityDefinition(TheirObject.class, "id"))
.build();

Mapstruct: Ignore specific field only for collection mapping

I am using following mapper to map entities:
public interface AssigmentFileMapper {
AssigmentFileDTO assigmentFileToAssigmentFileDTO(AssigmentFile assigmentFile);
AssigmentFile assigmentFileDTOToAssigmentFile(AssigmentFileDTO assigmentFileDTO);
#Mapping(target = "data", ignore = true)
List<AssigmentFileDTO> assigmentFilesToAssigmentFileDTOs(List<AssigmentFile> assigmentFiles);
List<AssigmentFile> assigmentFileDTOsToAssigmentFiles(List<AssigmentFileDTO> assigmentFileDTOs);
}
I need to ignore the "data" field only for entities that mapped as collection.
But it looks like #Mapping works only for single entities. Also I've noticed that generated method assigmentFilesToAssigmentFileDTOs just uses assigmentFileToAssigmentFileDTO in for-loop. Is there any solution for that?
MapStruct uses the assignment that it can find for the collection mapping. In order to achieve what you want you will have to define a custom method where you are going to ignore the data field explicitly and then use #IterableMapping(qualifiedBy) or #IterableMapping(qualifiedByName) to select the required method.
Your mapper should look like:
public interface AssigmentFileMapper {
AssigmentFileDTO assigmentFileToAssigmentFileDTO(AssigmentFile assigmentFile);
AssigmentFile assigmentFileDTOToAssigmentFile(AssigmentFileDTO assigmentFileDTO);
#IterableMapping(qualifiedByName="mapWithoutData")
List<AssigmentFileDTO> assigmentFilesToAssigmentFileDTOs(List<AssigmentFile> assigmentFiles);
List<AssigmentFile> assigmentFileDTOsToAssigmentFiles(List<AssigmentFileDTO> assigmentFileDTOs);
#Named("mapWithoutData")
#Mapping(target = "data", ignore = true)
AssignmentFileDto mapWithouData(AssignmentFile source)
}
You should use org.mapstruct.Named and not javax.inject.Named for this to work. You can also define your own annotation by using org.mapstruct.Qualifier
You can find more information here in the documentation.

Make Java annotation act differently depending on field annotated

In Java, is there a way to change the behaviour of an annotation depending on the type of the annotated field?
I know that annotation presence is supposed to be tested by code. Not the opposite. But the case is rather particular: this is a Jackson 2.0 « inside » annotation which gather a list of annotations. We use it to define the field name (#JsonProperty) and the field serializing policies (#JsonSerialize).
The serialisation policies must be adapted to the annotated field. And, because we are talking of a framework, one unique annotation is far better than two separate ones.
#Retention(RUNTIME)
#JacksonAnnotationsInside.
#JsonProperty("_id")
#JsonSerialize(using=IdSerializer.class)
#JsonDeserialize(using=IdDeserializer.class)
public #interface Id {}
Some cases need to turn the serializers down, that's the point. In the following example, the String must be processed by the de/serializers, ObjectId don't. Both need to be renamed _id by the #JsonProperty.
public class Car {
#Id String id
}
public class Bus {
#Id ObjectId id
}
Any clues?

Categories

Resources