Cannot map nested field with #Mapper - java

I'm trying to create a mapper using lombok #Mapper and I get the following error:
Property "delivery" has no write accessor in ClassA for target name "classBB_variable.delivery"
The code:
#Mapper
#Mapping(target = "classBB_variable.customerId", source = "classA_variable.customerId")
#Mapping(target = "classBB_variable.delivery", source = "classA_variable.classAA_variable.delivery")
ClassB toCreateRequest(ClassA classA_variable);
#Value
#Builder(toBuilder = true)
public class ClassA {
private UUID customerId;
private ClassAA classAA_variable;
}
#Data
#Builder
public class ClassAA {
private String delivery;
}
#Data
#Builder
public class ClassB {
private ClassBB classBB_variable;
}
#Data
#Builder
public class ClassBB {
private UUID customerId;
private String delivery;
}
As the map for customerId works fine, I'm assuming the issue is the additional level introduced with classA_variable.classAA_variable.delivery.
Has anyone already dealt with this situation? How can I solve it?

Related

Mapping nested list

I have a object with list of nested object
#Getter
#Setter
#NoArgsConstructor
public class Notification {
private Long id
private Long statusId;
private List <External> external;
}
#Getter
#Setter
#NoArgsConstructor
public class External{
private Long externalId;
private LocalDate date;
}
Dto
#Getter
#Setter
#NoArgsConstructor
public class NotificationPayload {
private Long id;
private Long statusId;
private List <ExternalReferencePayload> external;
}
#Getter
#Setter
#NoArgsConstructor
public class ExternalReferencePayload {
private Long externalReferenceId;
}
Mapper
#Mapper(componentModel = "spring")
public interface NotificationMapper{
public Notification dtoToNotification(NotificationPayload payload);
}
I search the way to map the nested list
In order to perform custom mapping for certain elements it is only needed to define a mapping method and MapStruct will take care of the rest. In your example:
#Mapper(componentModel = "spring")
public interface NotificationMapper{
public Notification dtoToNotification(NotificationPayload payload);
#Mapping(target = "externalId", source = "externalReferenceId")
public External dtoExternal(ExternalReferencePayload payload);
}
With this the nested list will use the dtoExternal mapping method to perform the mapping. With #Mapping you control how the mapping between externalId and externalReferenceId should be done

JsonDeserialize set inherited properties also when objectmapper readvalue

JsonDeserialize not working when objectmapper readvalue for inherited properties.
Vehicle Class
#Getter
#Setter
#JsonDeserialize(builder = Vehicle.VehicleBuilder.class)
#Builder(builderClassName = "VehicleBuilder", toBuilder = true)
public class Vehicle{
private String name;
private String noOfTyres;
#JsonPOJOBuilder(withPrefix = "")
public static class VehicleBuilder{
}
}
Car class
#Getter
#Setter
#JsonDeserialize(builder = Car.CarBuilder.class)
#Builder(builderClassName = "CarBuilder", toBuilder = true)
public class Car extends Vehicle {
private String carType;
#JsonPOJOBuilder(withPrefix = "")
public static class CarBuilder extends VehicleBuilder {
}
}
I don't want to create #NoArgsConstructor ,#AllArgsConstructor in both classes.
My issue when Car car = om.readValue(jsonValue,Car.class);
When I parse Json to java object the parent class properties are not setting properly.
As of now I'm using #NoArgsConstructor ,#AllArgsConstructor for work around for the use case.
Is there any way to use it along with #JsonDeserialize and #JsonPOJOBuilder?
The problem with the code is that it assumes that builders in inherited classes will set the parent properties as well. Unfortunately, they don't do that out of the box. However, this is something that can be achieved with Lombok but requires some additional code, as described in this post.
A complete solution could look as follows.
Parent Class
#Getter
#Setter
#JsonDeserialize
#Builder(builderClassName = "VehicleBuilder", builderMethodName = "vehicleBuilder")
public class Vehicle {
private String name;
private String noOfTyres;
}
Child Class
#Getter
#Setter
#JsonDeserialize(builder = Car.CarBuilder.class)
public class Car extends Vehicle {
private String carType;
#Builder
public Car(String name, String noOfTyres, String carType) {
super(name, noOfTyres);
this.carType = carType;
}
#JsonPOJOBuilder(withPrefix = "")
public static class CarBuilder extends VehicleBuilder {
}
}
Notice that the builder on the extending class is achieved by supplying a constructor with the #Builder annotation. Also take notice that the extending class does not set annotation parameter toBuilder=true as that will require access to parent properties which are private. This can be achieved by setting parent class properties to protected.

Mapstruct avoid circular issue with List<A> property in B class

If I have a Class ProfessorDto and a Class StudentDto how can I avoid circular issues if the ProfessorDto have a list of StudentDto and the StudentDto have a property of type ProfessorDto ?
I didn't put the code of the domain class but let's say it is the same as for the Dto.
I'm new to Mapstruct, converting a domain bean to a Dto with simple properties like Long, String is working but in my exemple, the relation OneToMany is not working !
#JsonApiResource(type = "professor")
#NoArgsConstructor
#Data
public class ProfessorDto {
#JsonApiId
private Long id;
private String professorName;
#JsonApiRelation(mappedBy = "professor")
private List<StudentDto> student;
public ProfessorDto(Long id) {
this.id = id;
}
}
And a class Student
#JsonApiResource(type = "student")
#NoArgsConstructor
#Data
public class StudentDto {
#JsonApiId
private Long id;
private String studentName;
#JsonApiRelation
private ProfessorDto professor;
}
My Mapper for Professor is
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ProfessorMapper {
ProfessorDto domainToDto(Professor domain);
Professor dtoToDomain(ProfessorDto dto);
StudentDto studentToDto(Student student);
Student studentDtoToDomain(StudentDto studentDto);
List<StudentDto> studentToDto(List<Student> student);
List<Student> studentDtoToDomain(List<StudentDto> studentDto);
}
check out this example using #Context annotation
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-mapping-with-cycles/src/main/java/org/mapstruct/example/mapper
more about the #Context annotation can be found at the Mapstruct documentation below
https://mapstruct.org/documentation/stable/api/org/mapstruct/Context.html
At first, you should decide if it is really needed to keep List<StudentDto> aggregated in ProfessorDto. You can just exclude it, if it's possible.
Otherwise, you can make either StudentDto or ProfessorDto 'flat'. For example, you can add field Long professorId instead of ProfessorDto professor to the StudentDto, or List<Long> studentIds to the ProfessorDto.
An sample approach using Mapstruct:
Change a DTO class like this:
#JsonApiResource(type = "student")
#NoArgsConstructor
#Data
public class StudentDto {
#JsonApiId
private Long id;
private String studentName;
private Long professorId; // <-- CHANGED
}
Add a Helper class, and provide custom mapping logic:
#Component
public class MapperHelper {
#AfterMapping
public void mapProfessorId(Student student, #MappingTarget StudentDto studentDto) {
studentDto.setProfessorId(student.getProfessor().getId());
}
}

Spring Data for Couchbase: use of AND ANY ... SATISFIES

I have a SpringBoot 2 app that uses using Couchbase as a database, Spring-Boot and Spring-Data and Lombok fot the getters and equals method
I have created this Repository
#ViewIndexed(designDoc = "bendicionesDoc")
public interface BenRepository extends CouchbaseRepository<BendicionesDoc, String> {
#Query("#{#n1ql.selectEntity} where #{#n1ql.filter} AND ANY uuid IN data.identifier.id SATISFIES uuid = $1 END")
List<BendicionesDoc<Item>> findById(String id);
}
and here all the objects created with Lombok library
public class BendicionesDoc<T>implements Serializable {
#Field
private T data;
}
and
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(NON_NULL)
public class Item {
private List<Identifier> identifier;
}
and
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(NON_NULL)
#EqualsAndHashCode
public class Identifier {
private String id;
private MasterServant idContext;
private MasterServant idScope;
}
and
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(NON_NULL)
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class MasterServant {
private String context;
#JsonValue
#EqualsAndHashCode.Include
private String value;
private Name valueDescription;
#JsonCreator
public MasterServant(String value) {
this.value = value;
}
}
But when I run the repository query I got always 0 results, even there are docs. in the DB:
You need to define your reference type in CouchbaseRepository<T, K> then simply add the reference type Item as CouchbaseRepository<BendicionesDoc<Item>, String> and just use Repository query keywords for findById(String id).
public interface BenRepository extends CouchbaseRepository<BendicionesDoc<Item>, String> {
List<BendicionesDoc<Item>> findById(String id);
}

Problems in Implementing a Custom Behaviour in mapstruct Mapper

I have the following DTO classes:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Conclusion {
private Integer id;
// ......
private List<CType> cTypes;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class CType {
private Integer id;
// ......
private VType vType;
}
And also their corresponding entity classes:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "conclusion")
public class Conclusion {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false)
private Integer id;
// ......
#OneToMany
#JoinColumn(name = "id")
private List<CTypeEntity> cTypeEntities;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "c_types")
public class CTypeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false)
private Integer id;
// ......
#Column(name = "v_type_id")
private Integer vTypeId;
}
Also, all corresponding Dao and JPA Repository interfaces are present.
Now, I am writing my mapstruct Mapper interface which should be used for mapping entities to DTOs and vice versa. Here is the mapper:
#Mapper
public interface ConclusionMapper {
#Mappings({
#Mapping(target = "cTypes", source = "cTypeEntities")
})
Conclusion toConclusion(ConclusionEntity entity);
List<Conclusion> toConclusionList(List<ConclusionEntity> entities);
#Mappings({
#Mapping(target = "cTypeEntities", source = "cTypes")
})
ConclusionEntity fromConclusion(Conclusion conclusion);
List<ConclusionEntity> fromConclusionList(List<Conclusion> conclusions);
#Mappings({
#Mapping(target = "cType", source = "vTypeId", qualifiedByName = "formVType")
})
ConclusionTaxType toCType(CTypeEntity cTypeEntity);
List<CType> toCTypes(List<CTypeEntity> cTypeEntities);
CTypeEntity fromCType(CType cType);
List<CTypeEntity> fromCTypeList(List<CType> cTypes);
#Named("formVType")
default VType formVType(CTypeEntity entity) {
// TODO: instantiate a DAO which will return an instance of VType
VTypeDao dao; // instantiate somehow
return vTypeDao.findById(entity.getVId());
}
}
VTypeDao looks like this:
public interface VTypeDao {
VType findById(int id);
boolean exists(int id);
List<VType> findAll();
}
#Component
public class VTypeDaoImpl implements VTypeDao {
private final VTypeRepo vTypeRepo;
public VTypeDaoImpl(VTypeRepo vTypeRepo) {
this.vTypeRepo = vTypeRepo;
}
// ............................. method implementations
}
My question is: How to instantiate an object of VTypeDao (or at least VTypeRepo so I could pass if to VTypeDaoImpl as a parameter)?
There is no factory class for getting the appropriate implementation of VTypeDao.
EDIT: VTypeDao and its implementation are third party components to my project.
From your comment I got you want to do lookups on your VTypeDao during the mapping process. You can wrap it in another class and hand it as #Context annotated as mapping argument. MapStruct will not consider such class as source or target. However it will call lifecycle methods in this class.. have a look at this example: https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-jpa-child-parent/src.
It's jpa based, but you can map it easily to your problem..
I eventually ended up with the following implementation:
#Mapper
public interface ConclusionMapper {
#Mappings({
#Mapping(target = "cTypes", source = "cTypeEntities")
})
Conclusion toConclusion(ConclusionEntity entity);
List<Conclusion> toConclusionList(List<ConclusionEntity> entities);
#InheritInverseConfiguration
ConclusionEntity fromConclusion(Conclusion conclusion);
List<ConclusionEntity> fromConclusionList(List<Conclusion> conclusions);
#Mappings({
#Mapping(target = "vType", ignore = true)
})
ConclusionTaxType toCType(CTypeEntity cTypeEntity);
List<CType> toCTypes(List<CTypeEntity> cTypeEntities);
#Mappings({
#Mapping(target = "vTypeId", source = "vType.id")
})
CTypeEntity fromCType(CType cType);
List<CTypeEntity> fromCTypeList(List<CType> cTypes);
}
So I just ignored vType member in entity to DTO mapping and put it manually in my DAO since that was the most simple way of doing.
Integrate map-struct with spring :
http://mapstruct.org/documentation/stable/reference/html/#using-dependency-injection
Replace #Mapper with below
#Mapper(componentModel = "spring")
and add below in VTypeDaoImpl
#Autowired
private ConclusionMapper conclusionMapper;
Execute below maven command (Assuming you are using maven)
mvn clean compile
sources will be generated in targeted/generated-sources/annotations

Categories

Resources