Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TargetType with Enums only works when mapper method is static #2458

Closed
amithkumarg opened this issue May 28, 2021 · 2 comments
Closed

TargetType with Enums only works when mapper method is static #2458

amithkumarg opened this issue May 28, 2021 · 2 comments

Comments

@amithkumarg
Copy link

amithkumarg commented May 28, 2021

MapStruct version: 1.4.2.Final

Here is my sample code setup:

interface EnumMessage {
  boolean carrier(String message);

  String getValue();

  default String getMessage() {
    return getValue().toUpperCase();
  }
}

enum EnumMessageA implements EnumMessage {
  GREAT("You are doing great"),
  GOOD("You are doing good"),
  OK("You are doing ok");

  private String value;

  EnumMessageA(final String value) {
    this.value = value;
  }

  @Override
  public String getValue() {
    return value;
  }

  @Override
  public boolean carrier(final String message) {
    return this.value.equals(message);
  }
}

class SourceX {
  public String name;
  public String message;
}

class TargetX {
  public String name;
  public EnumMessageA messageA;
}

@Mapper
interface EnumMapper {
  EnumMapper INSTANCE = Mappers.getMapper(EnumMapper.class);

  @Mapping(source = "message", target = "messageA", qualifiedByName = "stringToEnum")
  TargetX toTarget(SourceX source);

  @Mapping(source = "messageA", target = "message", qualifiedByName = "enumToString")
  SourceX toSource(TargetX target);

  @Named("stringToEnum")
  default <T extends EnumMessage> T mapStringToEnum(
      final String message, @TargetType final Class<T> enumClass) {
    final T[] values = enumClass.getEnumConstants();
    return Arrays.stream(values)
        .filter(enumValue -> enumValue.carrier(message))
        .findFirst()
        .orElse(null);
  }

  @Named("enumToString")
  default <T extends EnumMessage> String mapEnumToString(final T enumValue) {
    return enumValue.getMessage();
  }
}

Here is the test class:

 public class EnumMapperTest {
    
      @Test
      void checkMapping() {
        TargetX target = new TargetX();
        target.name = "MapStructTesting";
        target.messageA = EnumMessageA.GREAT;
        Assertions.assertEquals(
            "You are doing great".toUpperCase(), EnumMapper.INSTANCE.toSource(target).message);
    
        SourceX source = new SourceX();
        source.name = "MapStructTesting";
        source.message = "You are doing ok";
        Assertions.assertEquals(EnumMessageA.OK, EnumMapper.INSTANCE.toTarget(source).messageA);
      }
}

I get this compilation error:

 error: Qualifier error. No method found annotated with @Named#value: [ stringToEnum ]. See https://mapstruct.org/faq/#qualifier for more info.
      @Mapping(source = "message", target = "messageA", qualifiedByName = "stringToEnum")
     error: Can't map property "String message" to "EnumMessageA messageA". Consider to declare/implement a mapping method: "EnumMessageA map(String value)".
      @Mapping(source = "message", target = "messageA", qualifiedByName = "stringToEnum")

I only way to get this working for MapStruct to use the customer mapper method for string to enum, is move the mapStringToEnum inside a helper class make it static, and pass the helper class in @Mapper uses attribute.

So my modified code should look like this to work:

@Mapper
public class EnumMapperHelper {

  @Named("stringToEnum")
  public static <T extends EnumMessage> T mapStringToEnum(
      final String message, @TargetType final Class<T> enumClass) {
    final T[] values = enumClass.getEnumConstants();
    return Arrays.stream(values)
        .filter(enumValue -> enumValue.carrier(message))
        .findFirst()
        .orElse(null);
  }

  @Named("enumToString")
  //this doesn't have to be static but yes the @Qualifier is required
  public static <T extends EnumMessage> String mapEnumToString(final T enumValue) {
    return enumValue.getMessage();
  }
}


@Mapper(uses = EnumMapperHelper.class) 
interface EnumMapper {
  EnumMapper INSTANCE = Mappers.getMapper(EnumMapper.class);

  @Mapping(source = "message", target = "messageA", qualifiedByName = "stringToEnum")
  TargetX toTarget(SourceX source);

  @Mapping(source = "messageA", target = "message", qualifiedByName = "enumToString")
  SourceX toSource(TargetX target);
}
@Zegveld
Copy link
Contributor

Zegveld commented Jul 26, 2021

Your original code works for me.

I've got the following imports:

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.TargetType;
import org.mapstruct.factory.Mappers;

I've seen that the Named annotation exists in multiple packages so it might be that you had the wrong import for the annotation.
Not org.mapstruct.Named but for example javax.inject.Named.

@amithkumarg
Copy link
Author

Yes, you are right. I had the wrong import for Named annotation. Thanks for catching that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants