Create Task List Mapper
Now that we have our Task mapper implemented, let's create the mapper for TaskList. This mapper will be more complex than our Task mapper as it needs to handle the collection of tasks and calculate some derived fields like count and progress.
Creating the Mapper Interface
First, let's create the TaskListMapper interface in our com.devtiro.tasks.mappers package:
package com.devtiro.tasks.mappers;
import com.devtiro.tasks.domain.dto.TaskListDto;
import com.devtiro.tasks.domain.entities.TaskList;
public interface TaskListMapper {
TaskList fromDto(TaskListDto dto);
TaskListDto toDto(TaskList taskList);
}Like our TaskMapper interface, this defines two methods:
fromDto- converts aTaskListDtoto aTaskListentitytoDto- converts aTaskListentity to aTaskListDto
Implementing the Mapper
Now let's create TaskListMapperImpl in the com.devtiro.tasks.mappers.impl package. This implementation will be more complex as it needs to:
- Handle the conversion of the task collection
- Calculate the task count
- Calculate the progress (percentage of completed tasks)
Here's the implementation:
package com.devtiro.tasks.mappers.impl;
import com.devtiro.tasks.domain.dto.TaskListDto;
import com.devtiro.tasks.domain.entities.Task;
import com.devtiro.tasks.domain.entities.TaskStatus;
import com.devtiro.tasks.domain.entities.TaskList;
import com.devtiro.tasks.mappers.TaskMapper;
import com.devtiro.tasks.mappers.TaskListMapper;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class TaskListMapperImpl implements TaskListMapper {
private final TaskMapper taskMapper;
public TaskListMapperImpl(TaskMapper taskMapper) {
this.taskMapper = taskMapper;
}
@Override
public TaskList fromDto(TaskListDto dto) {
return new TaskList(
dto.id(),
dto.title(),
dto.description(),
Optional.ofNullable(dto.tasks())
.map(tasks -> tasks.stream()
.map(taskMapper::fromDto)
.toList())
.orElse(null),
null,
null
);
}
@Override
public TaskListDto toDto(TaskList taskList) {
final List<Task> tasks = taskList.getTasks();
return new TaskListDto(
taskList.getId(),
taskList.getTitle(),
taskList.getDescription(),
Optional.ofNullable(tasks)
.map(List::size)
.orElse(0),
calculateTaskListProgress(tasks),
Optional.ofNullable(tasks)
.map(t -> t.stream()
.map(taskMapper::toDto)
.toList())
.orElse(null)
);
}
private Double calculateTaskListProgress(List<Task> tasks) {
if(null == tasks) {
return null;
}
long closedTaskCount = tasks.stream()
.filter(task -> TaskStatus.CLOSED == task.getStatus())
.count();
return (double) closedTaskCount / tasks.size();
}
}Let's break down the key aspects of this implementation:
-
Dependency Injection
- We inject the
TaskMapperas we need it to convert individual tasks - Spring's dependency injection handles this through constructor injection
- We inject the
-
fromDto Method
- Maps basic fields directly (id, title, description)
- Handles the task collection conversion using the
TaskMapper - Sets created/updated to
nullas these are handled by the service layer - Uses
Optionalto safely handle null tasks collections
-
toDto Method
- Maps basic fields directly
- Calculates the task count using the size of the task collection
- Calculates the progress using a helper method
- Converts the task collection using the
TaskMapper - Uses
Optionalto safely handle null collections
-
calculateTaskListProgress Method
- Takes a list of tasks and calculates the completion percentage
- Returns
nullif the task list is null - Calculates the ratio of closed tasks to total tasks
- Returns a double between 0 and 1 representing progress
The use of Optional helps us handle null values safely and provides a clean way to chain operations on potentially null collections.
Summary
- Created the
TaskListMapperinterface to define the conversion contract - Implemented the
TaskListMapperinTaskListMapperImpl - Added helper method for progress calculation