Ambiguous mapping methods found for mapping property - java

I have two domain entities:
class Identity {
Long id;
Set<Business> businesses;
}
class Business {
Long id;
String name;
}
I then have two DTOs that extend a base DTO:
class BaseDto {
String id;
}
class IdentityDto extends BaseDto {
Set<BaseDto> businesses;
}
class BusinessDto extends BaseDto {
String name;
}
Then I created a mapper that maps a list of my domain entities to either a Set of the specific dto, or a set of the more generic base dto. This is because when I am getting a list of businesses, I want the full business dto, but when I get an identity, I just what the base info in it's list of businesses.
But when I try to create the mapper for the identity I get the following error:
Ambiguous mapping methods found for mapping property
"Set<Business> businesses" to Set<BaseDto>:
Set<BusinessDto> BusinessMapper.toSet(Set<Business> businesses),
Set<BaseDto> BusinessMapper.toBaseSet(Set<Business> businesses).
I thought that mapstruct used the most specific method, so should know to use the toIdentifierSet method in this case.
How do I make mapstruct know which method to use?

There is no most specific method here as you are trying to map into Set<BaseDto>.
You can use Mapping method selection based on qualifiers.
You can define some annotations:
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.CLASS)
public #interface BaseInfo {
}
Then in your BusinessMapper
#Mapper
public interface BusinessMapper {
Set<BusinessDto> toSet(Set<Business> businesses);
#BaseInfo
Set<BaseDto> toBaseSet(Set<Business> businesses);
}
Then in your identifier
#Mapper
public interface IdentifierMapper {
#Mapping(target = "businesses", qualifiedBy = BaseInfo.class)
IdentityDto map(Identity source);
}
In case you want to explicitly pick always you can add another annotation BusinessInfo and then annotate the other method. Then you would need to pick a method each time.

Related

How can I force Spring to serialize my controller's #ResponseBody as the method's declared return type?

I am attempting to use interfaces to define flexible response bodies from my Spring controllers.
What I expect: When I call an endpoint using Curl/Postman/etc, I should receive JSON objects that contain only the fields visible in the interface that the controller returns.
What I'm getting: When I call either endpoint, I receive JSON objects with every field defined in my entity.
Let's say my entity looks like this:
MyEntity.java
public class MyEntity implements ListEntityResponse, GetEntityResponse {
int dbid;
String name;
String description;
public int getDbid() { return dbid; }
public String getName() { return name; }
public String getDescription() { return description; }
}
Let's say MyEntity has many more fields that include complex data types that aren't suitable for serialization as part of a large list, or for certain other use cases. To solve this problem, I've created interfaces to limit which fields are visible in the response object. In this example, the first interface only defines two of the three getters, while the second interface defines all of them.
ListEntityResponse interface:
public interface ListEntityResponse {
int getDbid();
String getName();
}
GetEntityResponse interface:
public interface GetEntityResponse {
int getDbid();
String getName();
String getDescription();
}
And finally, here are my controllers. The important part is that each defines its return type as one of the interfaces:
ListEntityController
#GetMapping(path="/{name}")
public #ResponseBody List<ListEntityResponse> getList() {
return handler.getList(name);
}
GetEntityController
#GetMapping(path="/{name}")
public #ResponseBody GetEntityResponse getByName(#PathVariable("name") String name) {
return handler.getByName(name);
}
To recap, if we assume that our handler returns MyEntity objects, then I want that object to be serialized by Spring as the interface defined in the controller's return type. E.G. each JSON object in the list returned by the ListEntityController should have only the dbid and name fields. Unfortunately, that's not happening, and the returned JSON objects have every field available despite being masked as interface objects.
I have attempted to add #JsonSerialize(as = ListEntityResponse.class) to my first interface, and a similar annotation to the second. This works only if the entity implements just one of those interfaces. Once the entity implements multiple interfaces, each annotated with #JsonSerialize, Spring will serialize it as the first interface in the list regardless of the controller's return type.
How can I force a Spring to always serialize its Controller's responses as the controller function's return type?
Note: I am trying to find a solution that does not require me to use #JsonIgnore or #JsonIgnoreProperties. Additionally, I am trying to find a solution that does not require me to add #JsonView to my entity classes. I am willing to use the #JsonView annotation in the interfaces, but don't see a clean and maintainable way to do so.
How can I force Spring to always serialize its controller's responses as
the controller function's return type?
Please note that I am not interested in using #JsonIgnore,
#JsonIgnoreProperties, or #JsonView to provide the view masking that I
require. They do not fit my use case.
One of the options would be to create a thin wrapper over MyEntity class, which would be responsible for providing the required serialization-shape.
Every shape would be represented by its own wrapper, implemented as a single-field class. To specify serialization-shape, we can use as property of the #JsonSerialize annotation, by assigning the target interface as a value. And since we don't need the wrapper itself to reflected in the resulting JSON, we can make use of the #JsonUnwrapped annotation.
Here's a wrapper for GetEntityResponse shape:
#AllArgsConstructor
public class GetEntityResponseWrapper implements EntityWrapper {
#JsonSerialize(as = GetEntityResponse.class)
#JsonUnwrapped
private MyEntity entity;
}
And that's a wrapper for ListEntityResponse shape:
#AllArgsConstructor
public class ListEntityResponseWrapper implements EntityWrapper {
#JsonSerialize(as = ListEntityResponse.class)
#JsonUnwrapped
private MyEntity entity;
}
Basically, we have finished with serialization logic.
And you can use these lean classes in your controllers as is. But to make the solution more organized and easier to extend, I've introduced a level of abstraction. As you probably noticed both wrapper-classes are implementing EntityWrapper interface, its goal is to abstract away the concrete implementation representing shapes from the code in Controllers/Services.
public interface EntityWrapper {
enum Type { LIST_ENTITY, GET_ENTITY } // each type represents a concrete implementation
static EntityWrapper wrap(Type type, MyEntity entity) {
return switch (type) {
case LIST_ENTITY -> new ListEntityResponseWrapper(entity);
case GET_ENTITY -> new GetEntityResponseWrapper(entity);
};
}
static List<EntityWrapper> wrapAll(Type type, MyEntity... entities) {
return Arrays.stream(entities)
.map(entity -> wrap(type, entity))
.toList();
}
}
Methods EntityWrapper.wrap() and EntityWrapper.wrapAll() are uniform entry points. We can use an enum to represent the target type.
Note that EntityWrapper needs to be used in the return types in your Controller.
Here the two dummy end-points I've used for testing (I've removed the path-variables since they are not related to what I'm going to demonstrate):
#GetMapping("/a")
public List<EntityWrapper> getList() {
// your logic here
return EntityWrapper.wrapAll(
EntityWrapper.Type.LIST_ENTITY,
new MyEntity(1, "Alice", "A"),
new MyEntity(2, "Bob", "B"),
new MyEntity(3, "Carol", "C")
);
}
#GetMapping("/b")
public EntityWrapper getByName() {
// your logic here
return EntityWrapper.wrap(
EntityWrapper.Type.GET_ENTITY,
new MyEntity(2, "Bob", "B")
);
}
Response of the end-point "/a" (only two properties have been serialized):
[
{
"name": "Alice",
"dbid": 1
},
{
"name": "Bob",
"dbid": 2
},
{
"name": "Carol",
"dbid": 3
}
]
Response of the end-point "/b" (all three properties have been serialized):
{
"name": "Bob",
"description": "B",
"dbid": 2
}

How to specify default mapping method for Mapstruct

I have simple object Client
public class Client {
String externalCode;
String name;
String surname;
}
And I want to map it to nearly identical object
public class User {
String internalCode;
String name;
String surname;
}
See, I want externalCode to be mapped to internalCode. And I have a method which does that. I have marker my method with my custom #CodeMapping annotation and put that annotation to qualifiedBy parameter. So, here is my mapper.
#Mapper()
ClientMapper {
#CodeMapping
String toInternalCode(String externalCode) {
return externalCode + " internal part";
}
#Mapping(target = "internalCode", source = "externalCode", qualifiedBy = CodeMapping.class)
User toUser(Client client);
}
The problem is that name and surname fields are also mapped using toInternalCode method. Mapstruct sees that I have defined a method, which maps String to String and thinks it should be used in all cases.
Is there a way to tell Mapstruct, that if no qualifiers are specified, direct mapping should be used? Or make my own method which takes string and return it and tell Mapstruct it should use that method by default?
Most likely the toInternalCode is used by all methods because the #CodeMapping annotation is not meta annotated with #Qualifier (from org.mapstruct.Qualifier).
The #CodeMapping should be defined in the following way:
import org.mapstruct.Qualifier;
#Qualifier
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.CLASS)
public #interface CodeMapping {
}

MapStruct Is there a way to define defaut enum strategy for unknown value at class level?

I'm looking to define at mapper class level, a strategy that said if an enum value is not mapped, to map it to null.
Just like i would write at method level:
#ValueMapping( source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL )
So that it applies to all sub mapping method automaticaly generated by mapstruct, without forcing me to declare all of them just to add this line.
Create a 'class' mapper for your enum and then use it in other mapper with the uses attribute of the #Mapper annotation.
For example :
public interface MyEnumMapper {
#ValueMapping( source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL )
public String asString(MyEnum myenum);
}
and then to use it
#Mapper(uses=MyEnumMapper.class)
public class CarMapper {
CarDto carToCarDto(Car car);
}
(assuming Car object has an attribute of type MyEnum)
see the full documentation here : https://mapstruct.org/documentation/stable/reference/html/#invoking-other-mappers

Mapstruct: How to merge two fields into one

Using the MapStruct framework, how do you map multiple fields into a single one (based on custom logic) while still mapping the other fields one to one?
Here is a simple example to illustrate what I mean:
public class Source {
private String firstname;
private String surname;
// other fields eg:
private String address;
private int age;
private int favoriteNumber;
}
public class Target {
private String fullname; // Sould be firstname + surname
// other fields eg:
private String address;
private int age;
private int favoriteNumber;
}
I know it's possible using expressions:
#Mapping(target = "fullname", expression = "java(el.getFirstname() + el.getSurname())")
But in my special use case, not depicted in this example, I need to use some external library for the merging/mapping of the two fields, which isn't feasible with expressions.
Is there a way to achieve merging two fields without expressions?
One approach would be to add a custom mapping method from the Source object to the merged value, and then declare the source for the merged field to be the whole source object:
interface CustomMappingMethodMapper {
#Mapping(target = "fullname", source = ".")
Target map(Source source);
default String getFullName(Source s) {
return s.getFirstname() + " " + s.getSurname();
}
}
You can use #AfterMapping annotation
https://mapstruct.org/documentation/stable/reference/html/#customizing-mappings-with-before-and-after
You would like to replace your interface with abstract class and then
#AfterMapping
void customMapping(#MappingTarget Target target, Source source) {
// any custom logic
}
You claim that calling an external library in an expression isn't feasible. This may not be true, depending on the nature of the library you're calling and the frameworks being used.
Static method
If the method being called is a static method on a class, it can be called directly within the expression annotation element of #Mapping. To avoid having to fully qualify the class being called, the imports element of #Mapper can be used.
#Mapper(imports = ExternalLibrary.class)
public interface SourceToTargetMapper {
#Mapping(target = "fullname",
expression = "java(ExternalLibrary.toFullName(s.getFirstname(), s.getSurname()))")
Target map(Source s);
}
Spring Framework bean
If the library method is a method on a Spring bean, the mapper can be made into a bean using the Spring component model, and the Spring bean containing the library method can be injected into the mapper:
#Mapper(componentModel = "spring")
public static abstract class SourceToTargetMapper {
#Autowired
ExternalLibrary externalLibrary;
#Mapping(target = "fullname",
expression = "java(externalLibrary.toFullName(s.getFirstname(), s.getSurname()))")
abstract Target map(Source s);
}
To use this mapper, inject it as a Spring bean, and call the mapping method on the bean:
#Component
public class Example {
#Autowired
private SourceToTargetMapper mapper;
public void demonstrate(Source s) {
System.out.println(mapper.map(s));
}
}
I haven't tested it myself, but I imagine this injection approach would work with the other component models supported by MapStruct (cdi & jsr330).
I needed to compose 2 basic fields into am Wrapped Object. Here's how I did id
#Mapping(source = "quantity", target = "energyQuantity.quantity")
#Mapping(source = "quantityUnit", target = "energyQuantity.energyQuantityUnit", qualifiedByName = "tradeSearchEnergyQuantityUnitToEnergyQuantityUnit")
Trade tradeSearchToDomainDTO(api.client.generated.model.Trade trade);
where energyQuantity is a Wrapper class for the 2 fields

Mapstruct: map list properties by use of a qualified IterableMapping

At first, let me apologize if this question was already posed. I could not find any reference in StackOverflow.
I am trying to use a qualification in my mapping between beans via MapStruct, so as to convert between lists via a qualified mapping. Alas, without success.
Let us suppose we have the following classes (simplified as much as I can, and I will omit obvious getters/setters):
public class A {
private String propertyA;
}
public class B {
private String propertyB;
private A instanceA;
}
public class C {
private List<B> instancesB;
}
public class A1 {
private String propertyA;
}
public class B1 {
private String propertyB;
private A1 instanceA1;
}
public class C1 {
private List<B1> instancesB1;
}
Let us suppose to have the following qualifier:
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD })
public #interface Full{}
Let us also suppose to have the following mappings:
#Mapper
public interface MapperA {
A1 toA1(A a);
A toA(A1 a1);
}
#Mapper
public interface MapperB {
B1 toB1(B b);
#Mapping(source="propertyA", target="propertyA1")
#Full
B1 toB1Full(B b);
#IterableMapping(qualifiedBy=Full.class)
#Full
List<B1> toB1s(List<B> bs);
toB(B1 b);
#Mapping(source="propertyA1", target="propertyA")
#Full
toBFull(B1 b);
#IterableMapping(qualifiedBy=Full.class)
#Full
List<B> toBs(List<B1> bs);
}
#Mapper
public interface MapperC {
<HERE IS THE PROBLEM>
}
How can I write the Mapper C so as to use the full mapping for the iterated instances of B?
No matter how I try to write the annotations for the mapping, such as
#Mapping(source = "instancesB1", target="instancesB", qualifiedBy=Full.class)
I always find myself with an incomplete mapped entity: the B1 instance has a correctly mapped propertyB field, but no instanceA1.
I can of course just write MapperC as an abstract class, implement the method, call manually the mapper and just be happy with it, but I'm questioning whether it is possible to just annotate somehow the method in MapperC and have Mapstruct automagically use the correct mapping method for me (since this is an oversimplified case, but I may have tens of such lists to convert).
Thanks for the attention.
Not sure if you have tried that, but looking from the example it seems like the Mapper#uses is missing.
In your case it should look something like (omitted the mapping methods):
#Mapper
public interface MapperA {
}
#Mapper(uses = MapperA.class)
public interface MapperB {
}
#Mapper(uses = MapperB.class)
public interface MapperC {
}
When you use Mapper#uses then MapStruct will look for the qualified methods in the classes defined in uses. However, if you don't have that then there is nothing MapStruct could do and would generate some default mapping.

Categories

Resources