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 a TaskListDto to a TaskList entity
  • toDto - converts a TaskList entity to a TaskListDto

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:

  1. Handle the conversion of the task collection
  2. Calculate the task count
  3. 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:

  1. Dependency Injection

    • We inject the TaskMapper as we need it to convert individual tasks
    • Spring's dependency injection handles this through constructor injection
  2. fromDto Method

    • Maps basic fields directly (id, title, description)
    • Handles the task collection conversion using the TaskMapper
    • Sets created/updated to null as these are handled by the service layer
    • Uses Optional to safely handle null tasks collections
  3. 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 Optional to safely handle null collections
  4. calculateTaskListProgress Method

    • Takes a list of tasks and calculates the completion percentage
    • Returns null if 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 TaskListMapper interface to define the conversion contract
  • Implemented the TaskListMapper in TaskListMapperImpl
  • Added helper method for progress calculation
© 2026 Devtiro Ltd. All rights reserved