Jochem Kuijpers
Jochem Kuijpers

Reputation: 1797

How to simply unwrap an object with MapStruct?

I have a User ID class, that simply contains a String. My API exposes this as a String, so I want to 'unwrap' this value using MapStruct.

Example:

record UserId(String id) {}
@Mapper
public abstract class UserMapper {
    @Mapping(target = ".", source="userId.id")
    protected abstract String toApi(UserId userId);
}

I'm expecting this to work, even pointing out to MapStruct which member to use for the target value. The mapper should simply pass the String contained in UserId and pass it as an output. But instead MapStruct creates a new empty String and returns it without interacting with the source member (besides null-checks):

(generated code)

@Override
protected String toApi(UserId id) {
    if ( id == null ) {
        return null;
    }

    String string = new String();

    return string;
}

This is obviously wrong, I don't want a new empty string, I want the string contained in UserId. I can't figure out the right annotations to get this to work. Any thoughts?

My workaround listed below is writing a custom mapping method that does just this, but it seems weird that there's no way to let MapStruct generate this code for me.

@Mapper
public abstract class UserMapper {
    // ... other mapping methods ...
    
    protected String toApi(Userid userId) {
        return userId.id();
    }
}

Upvotes: 4

Views: 337

Answers (2)

Hasan Cetinkaya
Hasan Cetinkaya

Reputation: 31

You can use the expression attribute of @Mapping to directly call the getter method:

@Mapper
public abstract class UserMapper {
    @Mapping(target = ".", expression = "java(userId.id())")
    public abstract String toApi(UserId userId);
}

target = "." tells MapStruct that the target is not a field but the object itself.
expression = "java(userId.id())" let you use java code calling the id() method on the userId object.

Upvotes: 1

Nikolas
Nikolas

Reputation: 44476

As far as I know, this is not possible. MapStruct is used for field-to-field mapping (with accompanying, and String has no accessible fields to be mapped on.

The only solution I am aware of is to manually write the implementation.

default String toApi(UserId userId) {
    return userId.id();
}

If there is a possibility to implement an interface with a single method String id() and make it implement all the records to be mapped in the same way, the mapper becomes more flexible:

interface Identifiable {
    String id();
}

record UserId(String id) implements Identifiable {}
record UserId2(String id) implements Identifiable {}
default String toApi(Identifiable identifiable) {
    return identifiable.id();
}

This the same principle can be used with enum and the Enum class:

default String enumToName(Enum<?> enumerable) {
    return enumerable.name();
}

Upvotes: 0

Related Questions