Search code examples
tree-structuremodelmapper

ModelMapper and tree structures


I've got a tree-like structure of entities that I'm trying to map to DTOs using ModelMapper. The trick is that I'm trying to break the graph after the first parent.

Following is a example of what I'm trying to do. I've got a Category entity which has a name and a parent Category. I want my DTO to have a link to it's parent but don't want the latter to have it's own parent (so that I don't get the whole hierarchy)

The trouble is that ModelMapper does not map my DTO's parent because it's of a different type; I declared it as being a DTO rather that a CategoryDTO in order to break the recursion. If I DO declare my parent as a CategoryDTO, the mapping works fine but I will get all of my parents and grand parents (which I do not want).

Anybody knows how I could fix this?

import java.util.UUID;

import org.junit.Assert;
import org.junit.Test;
import org.modelmapper.ModelMapper;

public class CategoryTest {

    public static class Category {
        private String uid = UUID.randomUUID().toString();
        private String name;
        private Category parent;

        public Category () {}

        public Category(String name, Category parent) {
            this.name = name;
            this.parent = parent;
        }

        public String getUid() {return uid;}
        public void setUid(String uid) {this.uid = uid;}
        public String getName() {return name;}
        public void setName(String name) {this.name = name;}
        public Category getParent() {return parent;}
        public void setParent(Category parent) {this.parent = parent;}
    }

    public static class DTO {
        private String uid;
        private String name;

        public String getUid() {return uid;}
        public void setUid(String uid) {this.uid = uid;}
        public String getName() {return name;}
        public void setName(String name) {this.name = name;}
    }

    public static class CategoryDTO extends DTO {
        private DTO parent;

        public DTO getParent() {return parent;}
        public void setParent(DTO parent) {this.parent = parent;}
    }

    @Test
    public void simpleTest() {
        Category dto = new Category("Test1",null);

        CategoryDTO entity = new ModelMapper().map(dto, CategoryDTO.class);

        Assert.assertEquals("Test1", entity.getName());
        Assert.assertEquals(dto.getUid(), entity.getUid());
    }

    @Test
    public void withParentTest() {
        Category dto = new Category("child",new Category("root", null));

        CategoryDTO entity = new ModelMapper().map(dto, CategoryDTO.class);

        Assert.assertEquals("child", entity.getName());
        Assert.assertEquals(dto.getUid(), entity.getUid());
        Assert.assertNotNull(entity.getParent());
    }
}

Solution

  • I've managed to solve my problem by customizing my ModelMapper with a Converter. My ModelMapper is now fetched by calling this method:

    private ModelMapper getModelMapper() {
        ModelMapper map = new ModelMapper();
        map.addConverter(new AbstractConverter<Category, AbstractDTO>() {
    
            @Override
            protected AbstractDTO convert(Category source) {
                if (source == null) return null;
    
                AbstractDTO dto = new AbstractDTO();
                dto.setUid(source.getUid());
                dto.setName(source.getName());
                return dto;
            }
    
        });
        return map;
    }