Generic mapping of Value Objects with mapstruct

yunandtidus :

Trying to use value objects in my business model, I'm facing issues with the following code :

@Mapper
public abstract class TestMapstruct {

    public ValueObjectA mapA(String value){
        return new ValueObjectA(value);
    }

    public abstract BusinessObject map(DTO dto);

    @Value
    public static class ValueObjectA {
        private String a;
    }

    @Value
    public static class ValueObjectB {
        private String b;
    }

    @Data
    public static class BusinessObject {
        private ValueObjectA a;
        private ValueObjectB b;
    }

    @Data
    public static class DTO {
        private String a;
        private String b;
    }
}

Missing mapping (String -> ValueObjectB) would lead to the following compilation error message :

Can't map property "java.lang.String b" to "test.ValueObjectB b". Consider to declare/implement a mapping method: "test.ValueObjectB map(java.lang.String value)".

I totally understand this, but I would rather not to declare a method for each of my ValueObjects (could have dozens in a project).

Is there a generic way to declare (String -> ValueObject) mapping method ?

Filip :

There is no generic way to do this if you don't have a common interface between all the methods.

However, if you have a common interface then it is possible. For example:

public interface ValueObject<T> {

    T getValue();

    void setValue(T value);

}

Then you need a helper mapper:

public interface ValueObjectMapper {


    static <V extends ValueObject<T>, T> T mapToValue(V valueObject) {
        return valueObject == null ? null : valueObject.getValue();
    }

    static <V extends ValueObject<T>, T> V mapFromValueObject(T value, @TargetType Class<V> valueObjectClass) {
        if (value == null) {
            return null;
        }


        try {
            V valueObject = valueObjectClass.getDeclaredConstructor().newInstance();
            valueObject.setValue(value);

            return valueObject;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

}

Edit: Add example with immutable Value objects.

In case you want your value objects to be immutable then you can do the following:

public interface ValueObject<T> {
    T getValue();
}


public interface ValueObjectMapper {


    static <V extends ValueObject<T>, T> T mapToValue(V valueObject) {
        return valueObject == null ? null : valueObject.getValue();
    }

    static <V extends ValueObject<T>, T> V mapFromValueObject(T value, @TargetType Class<V> valueObjectClass) {
        if (value == null) {
            return null;
        }


        try {
            V valueObject = valueObjectClass.getDeclaredConstructor(value.getClass()).newInstance(value);

            return valueObject;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

}

Note: To make this work you will have to make sure that all your ValueObjects have a constructor with that value.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related