terça-feira, 27 de janeiro de 2026

📝 Mapeamento para String com MapStruct: Convertendo Enums, Datas e Objetos em Java

1. Visão Geral

Ao trabalhar com aplicações Java, muitas vezes precisamos converter objetos de dados complexos em representações mais simples. Por exemplo, é comum converter tipos de dados como enums, números ou objetos aninhados para seus equivalentes em String para exibição, registro em log (logging) ou respostas de API. Nesse cenário, podemos usar o MapStruct.

Neste tutorial, vamos mapear diferentes tipos de dados para uma String.


2. Configuração do Projeto

Para demonstrar o mapeamento para String no MapStruct, podemos criar um projeto Maven simples mapstructstringmapping.

No arquivo pom.xml, vamos navegar até o diretório mapstructstringmapping e atualizar:

<dependencies>
    <!-- MapStruct -->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.6.3</version>
    </dependency>

    <!-- JUnit 5 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.14.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Acima, adicionamos mapstruct para mapeamento em tempo de compilação e JUnit 5 para testes.


3. Mapeamento Simples para String

Ao trabalhar em apresentações ou mesmo em respostas de API, podemos precisar converter valores numéricos ou booleanos em strings. Um bom exemplo é quando uma entidade de banco de dados armazena a idade de um usuário como um int, mas a camada de API precisa expô-la como uma String.

Vamos examinar um exemplo direto para mostrar como usar o MapStruct para mapear um campo int para uma String. Primeiro, definimos a classe Person:

public class Person {
    private String name;
    private int age;
}

Em seguida, vamos criar a segunda classe PersonDTO:

public class PersonDTO {
    private String name;
    private String age;
}

Nos exemplos acima, as classes possuem getters e setters padrão, que o MapStruct utilizará para o mapeamento.

Agora, vamos definir a interface do mapper PersonMapper.java:

@Mapper
public interface PersonMapper {

    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
    PersonDTO toDTO(Person person);
}

Em tempo de compilação, o MapStruct cria a implementação de PersonMapper. Neste caso, o MapStruct automaticamente altera o valor int de age do objeto de origem para uma String no objeto de destino quando o método toDTO() é chamado.

Vamos criar o teste unitário PersonMapperUnitTest.java e garantir que o mapeamento funciona como pretendido:

public class PersonMapperUnitTest {

    @Test
    void givenPerson_whenMapsToPersonDTO_thenFieldsAreCorrect() {

        Person person = new Person();
        person.setName("Alice");
        person.setAge(30);

        PersonDTO dto = PersonMapper.INSTANCE.toDTO(person);

        assertEquals("Alice", dto.getName());
        assertEquals("30", dto.getAge());
    }
}

O teste confirma que o valor int da idade é traduzido para sua representação em String e que o campo name é mapeado com sucesso.


4. Conversão de Enums para String

Em Java, enums são amplamente usados para definir conjuntos fixos de variáveis, incluindo funções de usuário, status de pedidos e estados do sistema. No entanto, quando os dados são mostrados em interfaces do usuário ou tornados acessíveis através de APIs, esses valores de enum geralmente são renderizados como strings.

4.1. Definindo o Enum e as Classes de Domínio

Vamos começar definindo um enum que representa o status de um usuário:

public enum Status {
    ACTIVE,
    INACTIVE,
    PENDING
}

Para seguir em frente, vamos criar um objeto de domínio que usa este enum:

public class User {
    private String username;
    private Status status;
}

Por fim, criamos um DTO onde o valor do enum é representado como uma String:

public class UserDTO {
    private String username;
    private String status;
}

Agora, a classe User usa um enum para o campo status, enquanto UserDTO espera o mesmo valor como uma String.

4.2. Definindo o Mapper

Vamos definir uma interface de mapper que converte um User em um UserDTO:

@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    UserDTO toDto(User user);
}

Aqui, o MapStruct detecta automaticamente que o campo de origem é um enum e o campo de destino é uma String. Não precisamos definir nenhuma lógica de mapeamento personalizada. Ele invoca o método name() do enum durante o processo de mapeamento.

Na maioria das APIs do mundo real, esse comportamento padrão é suficiente, mas é importante estar ciente disso se os nomes dos enums não corresponderem aos valores esperados pelos clientes.

4.3. Verificando o Mapeamento

Vamos escrever um teste unitário que confirma que a conversão de enum para string funciona conforme o esperado:

public class UserMapperUnitTest {

    @Test
    void shouldMapEnumToString() {

        User user = new User();
        user.setUsername("Kevin");
        user.setStatus(Status.ACTIVE);

        UserDTO dto = UserMapper.INSTANCE.toDto(user);

        assertEquals("Kevin", dto.getUsername());
        assertEquals("ACTIVE", dto.getStatus());
    }
}

Isto é o que o teste verifica:

  • O campo username é mapeado corretamente
  • O valor do enum Status.ACTIVE é convertido para sua representação em String, “ACTIVE”

Notavelmente, este comportamento funciona "pronto para uso" com MapStruct e não requer configuração adicional.


5. Mapeando Datas para String

Ao trabalhar com campos de data, muitas vezes precisamos convertê-los em representações de string formatadas antes de expô-los através de APIs ou exibi-los em interfaces do usuário. O MapStruct suporta este caso de uso através do atributo dateFormat na anotação @Mapping.

Usando este atributo, podemos definir o padrão de data exato que queremos que o MapStruct aplique durante o processo de mapeamento.

5.1. Definindo as Classes de Domínio e DTO

Vamos começar definindo uma entidade de domínio simples que contém um campo LocalDate:

public class Event {
    private String name;
    private LocalDate eventDate;
}

Em seguida, vamos definir um DTO correspondente onde o campo eventDate é representado como uma String:

public class EventDTO {
    private String name;
    private String eventDate;
}

Aqui, a classe Event representa o modelo de domínio, enquanto EventDTO é o objeto de transferência de dados usado para saída. A diferença principal é que o campo eventDate é um LocalDate no objeto de origem e uma String formatada no objeto de destino.

5.2. Criando o Mapper

Agora, vamos definir uma interface de mapper para converter um Event em um EventDTO:

@Mapper
public interface EventMapper {

    EventMapper INSTANCE = Mappers.getMapper(EventMapper.class);

    @Mapping(source = "eventDate", target = "eventDate", dateFormat = "yyyy-MM-dd")
    EventDTO toEventDTO(Event event);
}

Aqui, @Mapping especifica que o campo eventDate deve ser formatado de acordo com o padrão "yyyy-MM-dd" (por exemplo, "2024-12-31"). O MapStruct cuida da conversão do objeto LocalDate para a representação de string correspondente.

5.3. Testando a Conversão de Data

Para garantir que o mapeamento e a formatação funcionam corretamente, podemos escrever um teste unitário:

public class EventMapperUnitTest {

    @Test
    void shouldMapLocalDateToStringUsingProvidedFormat() {

        Event event = new Event();
        event.setName("Conferência");
        event.setEventDate(LocalDate.of(2024, 12, 31));

        EventDTO dto = EventMapper.INSTANCE.toEventDTO(event);

        assertEquals("Conferência", dto.getName());
        assertEquals("2024-12-31", dto.getEventDate());
    }
}

O teste acima confirma que:

  • O campo name é mapeado sem alterações
  • O objeto LocalDate é formatado corretamente no padrão especificado "yyyy-MM-dd"

O uso do atributo dateFormat é particularmente útil para garantir a consistência dos formatos de data em diferentes pontos de extremidade da API ou componentes da interface do usuário.


6. Considerações Adicionais e Melhores Práticas

Lidando com Valores Nulos

Em cenários do mundo real, os dados de origem podem conter valores nulos. O MapStruct lida com eles de forma segura por padrão, mas é importante testar e documentar o comportamento esperado em seus mapeadores.

Customizando Conversões Complexas

Para cenários mais complexos (por exemplo, formatar números com casas decimais específicas, converter objetos aninhados em strings JSON), você pode usar métodos default dentro da interface do mapper ou definir um método de mapeamento personalizado usando a anotação @Named.

Testes Abrangentes

Sempre escreva testes para seus mapeadores, cobrindo casos de uso comuns, casos de borda (valores nulos, enums desconhecidos) e formatos específicos. Isso garante que as conversões de dados permaneçam confiáveis conforme sua aplicação evolui.


7. Conclusão

Neste tutorial, exploramos como usar o MapStruct para mapear vários tipos de dados para suas representações em String. Vimos como o mapeamento direto de tipos primitivos como int funciona automaticamente, como converter valores de enum usando o método name() e como formatar objetos LocalDate usando o atributo dateFormat na anotação @Mapping.

💡 O MapStruct simplifica significativamente a conversão de dados em aplicações Java, fornecendo mapeamento seguro de tipos em tempo de compilação. Ao utilizar esses padrões, podemos manter nosso código limpo, legível e fácil de manter ao lidar com diferentes representações de dados em nossas aplicações.