For some context, I am developing a Java Web application with Spring & Lombok (Rest API, controllers, etc).
And for JSON API responses I am using Mapstruct to perform the serializations between internal models and the final external ones.
But I cannot figure out how to parse the following example:
Where we can have a Tree.
public class Tree {
List<Leaf> leafs;
}
And inside of it we have Leafs. But each Leaf as inside of it a list of Leafs.
public class Leaf {
String name;
List<Leaf> children;
}
So what I would need Mapstruct to do is, serialize the Tree into a TreeResponse.
public class TreeResponse {
List<LeafResponse> leafs;
}
And all Leafs inside the Tree into LeafResponse, along with the Leaf list inside the "father" Leaf.
public class LeafResponse {
String name;
List<LeafResponse> children;
}
How can I achieve this in Mapstruct? Thanks
Leaving all classes and simple test that finally worked.
Tree.java
#Builder
#Getter
#AllArgsConstructor
#FieldDefaults(level = PUBLIC)
public class Tree {
String name;
List<Leaf> leafs;
}
Leaf.java
#Builder
#Getter
#AllArgsConstructor
#FieldDefaults(level = PUBLIC)
public class Leaf {
String name;
List<Leaf> children;
}
TreeResponse.java
#Getter
#Setter
#FieldDefaults(level = PUBLIC)
public class TreeResponse {
String name;
List<LeafResponse> leafs;
}
LeafResponse.java
#Getter
#Setter
#FieldDefaults(level = PUBLIC)
public class LeafResponse {
String name;
List<LeafResponse> children;
}
Mappers
#Mapper
public interface TreeMapper {
#Mapping(target = "name", source = "entity.name")
TreeResponse map(Tree entity);
}
#Mapper
public interface LeafMapperSecond {
LeafResponse map(Leaf entity);
List<LeafResponse> map(List<Leaf> entity);
}
Test
private TreeMapper treeMapper = Mappers.getMapper(TreeMapper.class);
#Test
public void test() {
List<Leaf> leafs = new ArrayList<>();
leafs.add(Leaf.builder().name("Leaf 1").build());
leafs.add(
Leaf.builder()
.name("Leaf 2")
.children(
Arrays.asList(
Leaf.builder()
.name("Leaf Children 1")
.children(
Arrays.asList(
Leaf.builder()
.name("Leaf Children 1.1")
.build(),
Leaf.builder()
.name("Leaf Children 1.2")
.build()))
.build(),
Leaf.builder().name("Leaf Children 2").build()))
.build());
Tree tree = Tree.builder().name("tree name").leafs(leafs).build();
TreeResponse treeResponse = treeMapper.map(tree);
assertEquals(treeResponse.name, "tree name");
assertEquals(treeResponse.leafs.size(), 2);
LeafResponse leafWithChildren =
treeResponse.leafs.stream()
.filter(l -> l.name.equals("Leaf 2"))
.findFirst()
.orElse(null);
assertNotNull(leafWithChildren);
assertEquals(leafWithChildren.getChildren().size(), 2);
LeafResponse leafWithSubChildren =
leafWithChildren.children.stream()
.filter(l -> l.name.equals("Leaf Children 1"))
.findFirst()
.orElse(null);
assertNotNull(leafWithSubChildren);
assertEquals(leafWithChildren.getChildren().size(), 2);
}
Related
how can I map difference model list string to list model my code below;
my TaskList.class
#Getter
#Setter
private List<TaskGroupList> groupIds;
my TaskResponse.class
#Getter
#Setter
private List<String> groupIds;
My TaskGroupList class
#Getter
#Setter
private String ownerId;
My TaskListMapper.class
public abstract List<TaskResponse> toAllTaskListResponse(List<TaskList> taskList);
For mapping to a List, I think you need to use IterableMapping annotation. Refer below link -
https://mapstruct.org/documentation/stable/reference/html/#implicit-type-conversions
It has an example for list to list conversion
#Mapper
public interface CarMapper {
#IterableMapping(numberFormat = "$#.00")
List<String> prices(List<Integer> prices);
}
So, in your scenario, you could use the annotation. Also, as there is a List of List Objects, nested iterations are needed as below -
#IterableMapping(qualifiedBy = IterableMapping.class)
List<TaskResponse> toAllTaskListResponse(List<TaskList> taskList);
#IterableMapping(qualifiedByName = "taskGroup")
List<String> map(List<TaskGroupList> task);
#Named("taskGroup")
default String map(TaskGroupList t) {
return t.getOwnerId();
}
#Mapper(componentModel = "spring")
public interface CarListMapper {
#Mapping(source = "groupIds",target = "groupIds",qualifiedByName = "toGroupId")
TaskResponse toTaskResponse(TaskList taskList);
List<TaskResponse> fromAllTaskList(List<TaskList> carDtoList);
#Named("toGroupId")
static List<String> toGroupId(List<TaskGroupList> groupIds){
return groupIds.stream()
.map(taskGroupList -> taskGroupList.getOwnerId()).collect(Collectors.toList());
}
}
It worked for me
I am trying to use ModelMapper to map a collection of sub-objects to a list of id numbers.
public class Child {
private int id;
private String childName;
}
public class Parent {
private Set<Child> children;
private String parentName;
}
public class ParentDto {
private int[] children; // Or use ArrayList<Integer>
private String parentName;
}
How do I tell ModelMapper to flatten the set of Child objects to an array of id numbers?
My first attempt is this but does not seem correct:
modelMapper.addMappings(new PropertyMap<Parent, ParentDto>() {
#Override
protected void configure() {
using(ctx -> ctx.getSource().stream().map(Parent::getId).collect(Collectors.toList())
.map().setChildren(source.getId()));
};
});
Type listType = new TypeToken<ArrayList<ParentDto>>() {}.getType();
ArrayList<ParentDto> parentDtos = new ArrayList<>();
parentDtos = modelMapper.map(parents, listType);
The setChildren call seems like it needs to be map().add() but that does not work either.
Ideas are welcome.
One way of doing this is to create a Set<Child> to int[] converter and use it to map Parent.children to ParentDTO.children:
ModelMapper mapper = new ModelMapper();
Converter<Set<Child>, int[]> childSetToIntArrayConverter =
ctx -> ctx.getSource()
.stream()
.mapToInt(Child::getId)
.toArray();
mapper.createTypeMap(Parent.class, ParentDto.class)
.addMappings(map -> map
.using(childSetToIntArrayConverter)
.map(
Parent::getChildren,
ParentDto::setChildren
)
);
Here is the complete demo (using lombok 1.18.10 and modelmapper 2.3.5):
import lombok.AllArgsConstructor;
import lombok.Data;
import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Parent parent = new Parent();
parent.setParentName("Parent");
parent.setChildren(Set.of(
new Child(1, "A"),
new Child(2, "B"),
new Child(3, "C")
));
ModelMapper mapper = new ModelMapper();
Converter<Set<Child>, int[]> childSetToIntArrayConverter =
ctx -> ctx.getSource()
.stream()
.mapToInt(Child::getId)
.toArray();
mapper.createTypeMap(Parent.class, ParentDto.class)
.addMappings(map -> map
.using(childSetToIntArrayConverter)
.map(
Parent::getChildren,
ParentDto::setChildren
)
);
ParentDto dto = mapper.map(parent, ParentDto.class);
System.out.println(parent);
System.out.println(dto);
}
#Data
#AllArgsConstructor
public static class Child {
private int id;
private String childName;
}
#Data
public static class Parent {
private Set<Child> children;
private String parentName;
}
#Data
public static class ParentDto {
private int[] children;
private String parentName;
}
}
Output
Main.Parent(children=[Main.Child(id=3, childName=C), Main.Child(id=2, childName=B), Main.Child(id=1, childName=A)], parentName=Parent)
Main.ParentDto(children=[3, 2, 1], parentName=Parent)
I'm trying to make sortable/pageable/filterable repository with multiple filter methods. This is how my relevant code looks right now:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", length = 50, nullable = false)
private String name;
The repository:
#Repository
public interface UserRepository extends JpaRepository<User, Long> ,
QuerydslPredicateExecutor<User> {
}
And the controller:
#RequestMapping(path="/test")
public #ResponseBody ResponseEntity<Object> foo
( #QuerydslPredicate(root = User.class) Predicate predicate, Pageable pageable) {
return userRepository.findAll(predicate,pageable);
}
It is working perfectly fine, like this:
/users/?page=0&limit=1&sort=name,ASC&name=testuser
But i can't use any other filter method except equals like "name=testuser"
I was searching around and i keep finding guides like this but i'd have to write a PathBuilder for every entity and the controller looks way uglier too.
Is there a way to work around this and keep everything simplified like now? I need the basic operators like eq,neq,gte,lte,like, etc...
Generally I use the CriteriaBuilder API. And it gives me a small solution, all you need to do is subscribe the repository to your custom spec.
public class CustomerSpecification implements Specification<CustomerDetail> {
private C2Criteria criteria;
public static CustomerSpecification of(C2Criteria criteria) {
return new CustomerSpecification(criteria);
}
private CustomerSpecification(C2Criteria criteria) {
this.criteria = criteria;
}
#Override
public Predicate toPredicate
(Root<CustomerDetail> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return getPredicate(root, builder, criteria);
}
}
public <T> Predicate getPredicate(Root<T> root, CriteriaBuilder builder, C2Criteria criteria) {
if (criteria.getOperation().equalsIgnoreCase(">")) {
return builder.greaterThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString());
} else if (criteria.getOperation().equalsIgnoreCase("<")) {
return builder.lessThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString());
} else if (criteria.getOperation().equalsIgnoreCase(":")) {
if (root.get(criteria.getKey()).getJavaType().equals(String.class)) {
return builder.like(
root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
}
}
And my criteria class is:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class C2Criteria {
private String key;
private String operation = ":";
private Object value;
}
And my JpaRepository looks like:
public interface CustomerDetailRepository extends JpaRepository<CustomerDetail, Long>, JpaSpecificationExecutor<CustomerDetail> {
}
In your controller you can use it by getting the object from the queryString.
#GetMapping(value = "renew")
public ResponseEntity renew(#NonNull PageDto page, #NonNull C2Criteria criteria) {
Page<InsuranceRenew> renews = this.insuranceService.getRenew(page, criteria);
return ResponseEntity.ok(renews);
}
and the insuranceservice method looks like:
#Override
public Page<InsuranceRenew> getRenew(#NonNull PageDto page, #NonNull C2Criteria criteria) {
Pageable pageable = PageRequest.of(page.getPage(), page.getSize(), new Sort(page.getSort(), page.getOrderBy()));
InsuranceRenewSpecification specification = InsuranceRenewSpecification.of(criteria);
return this.renewRepository.findAll(specification, pageable);
}
You can see that I used a PageDto class, which is just a POJO with some fields for pagination purposes and it is defined as:
#Data
public class PageDto {
private int page;
private int size = 10;
private Sort.Direction sort = Sort.Direction.DESC;
private String orderBy = "id";
}
As you can see, I used to use the id as the default order by to prevent no wanted exceptions and de order DESC as default.
Hope it helps.
Given this 3 entities:
#Entity
class Department{
Set<Employee> employees;
Set<Employee> getEmployees(){
return this.employees;
};
}
#Entity
class Employee{
Nationality nationality;
Nationality getNationality(){
this.nationality;
}
}
#Entity
class Nationality{
}
I want to create a projection for Department that returns all departments with their employees and nationalities. What I have achieved is to return all departments with their employees using:
#Projection(name = "fullDepartment", types = { Department.class })
public interface DepartmentsProjection {
Set<Employee> getEmployees();
}
#RepositoryRestResource(collectionResourceRel = "department", path = "departments")
public interface DepartmentRepository extends JpaRepository<Department, Long> {
}
A way to do this is to create a Projection for your nested(s) object, and then use this projection in a more global one. So following your problem, you can create a projection for Nationality, then another one for Department that has a getter to catch Nationality's projection, and finally another projection to get Department's entity.
#Projection(name = "NationalityProjection", types = { Nationality.class })
public interface NationalityProjection{
// getters of all attributes you want to project
}
#Projection(name = "EmployeeProjection", types = { Employee.class })
public interface EmployeeProjection{
NationalityProjection getNationality();
}
#Projection(name = "DepartmentProjection", types = { Department.class })
public interface DepartmentProjection{
Set<EmployeeProjection> getEmployees();
}
Hope it helps!
I'm trying to add JPA annotations to my model.
Here's a part of my superclass:
#Entity
#Table(name="destinations")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type")
public abstract class Destination {
protected abstract Destination getParent();
protected abstract Set<Destination> getChildren();
}
And these are the implementations:
#Entity
#DiscriminatorValue(value = "country")
public class Country extends Destination {
private Set<Destination> regions;
private Set<Destination> cities;
#Override
public Destination getParent() {
return null;
}
#Override
public Set<Destination> getChildren() {
if (regions != null && !regions.isEmpty()) {
return regions;
}
return cities;
}
}
#Entity
#DiscriminatorValue(value = "region")
public class Region extends Destination {
private Set<Destination> cities;
private Country country;
#Override
public Destination getParent() {
return country;
}
#Override
public Set<Destination> getChildren() {
return cities;
}
}
#Entity
#DiscriminatorValue(value = "city")
public class City extends Destination {
private Country country;
private Region region;
#Override
public Destination getParent() {
if (region != null) {
return region;
}
return country;
}
#Override
public Set<Destination> getChildren() {
return null;
}
}
So there are Countries that have Regions with Cities in it, and Countries without Regions but with Cities.
How do I annotate these classes so their bidirectional relationships can be persist and all the Destinations are in a single table?
Ideally as my understanding the design should have been this way
Country <----> City
Country <----> Region
City <----> Region
Considering that region can be or cannot exist we need to have Country to City mapping mandatory and optional mapping related to Region.
Now as per your requirement where in you need all of them associated to Destination which means as per the current design shown above you will have multiple values
for eg. Country(IND)-Region(MH)-City(MUM); Then you would 2 rows in destination one for Country-City and other Country-Region
Hence my final conclusion is that if you go for Inheritance Design you will end up having multiple rows as explained in the previously example and if you simply use OneToOne/OneToMany mapping as shown in point you persist twice once for Country-City and Country-Region