Исправлена ошибка для неоднозначных методов сопоставления, обнаруженная для ошибки свойства сопоставления в @Квалификаторе с тем же типом метода

#java #mapstruct

Вопрос:

Я получаю неоднозначную ошибку отображения при «отображении с карты» с помощью MapStruct 1.3.1.Final

 @Mapper( uses = MappingUtil.class )
public interface SourceTargetMapper {

    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping(source = "map", target = "aProperty", qualifiedBy = A.class )
    @Mapping(source = "map", target = "bProperty", qualifiedBy = B.class )
    Target toTarget(Map<String,Map<String, Object>> map);
}


public class MappingUtil {

  @Qualifier
  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.CLASS)
  public @interface A {
  }

  @Qualifier
  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.CLASS)
  public @interface B {
  }

  @A
  public String abc(Map<String,Map<String, Object>> in) {
     return (String) in.get("first_key").get("a_second_key");
  }

  @B
  public String xyz(Map<String,Map<String, Object>> in) {
     return (String) in.get("first_key").get("b_second_key");
  }

}
 

Когда я изменяю @A или @B на другой тип, ошибка исчезает

   @B
  public int xyz(Map<String,Map<String, Object>> in) {
     return (int) in.get("first_key").get("b_second_key");
  }
 

Чего мне не хватает? В примере с документами использовались два метода одного и того же типа, поэтому это не должно быть проблемой

ИЗМЕНИТЬ: Та же проблема в Mapstruct 4.1.2.Финал

Комментарии:

1. Вы пробовали 1.4.2.Final?

2. Только что попробовал, та же проблема

Ответ №1:

Ваша реализация квалификаторов неверна. Вот ссылка, которая показывает, как реализовать пользовательское сопоставление с помощью квалификаторов в случае неоднозначных сопоставлений.

Ниже приведена краткая версия кода о том, как вы можете создавать пользовательские сопоставления на основе квалификаторов:

 @Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Translator {
}

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface A {
}

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface B {
}

@Translator
public class MappingUtil {
    @A
    public String abc(Map<String,Map<String, Object>> in) {
        return (String) in.get("first_key").get("a_second_key");
    }

    @B
    public String xyz(Map<String,Map<String, Object>> in) {
        return (String) in.get("first_key").get("b_second_key");
    }
}

@Mapper( uses = MappingUtil.class )
public interface SourceTargetMapper {

    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping(source = "map", target = "aProperty", qualifiedBy = { Translator.class, A.class } )
    @Mapping(source = "map", target = "bProperty", qualifiedBy = { Translator.class, B.class } )
    Target toTarget(Map<String,Map<String, Object>> map);
}
 

Альтернатива:

Вы можете использовать qualifiedByName , если не хотите создавать отдельный служебный класс. Ниже приведен пример кода для этого:

 @Mapper
public interface SourceTargetMapper {

    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping(source = "map", target = "aProperty", qualifiedByName= "abc" )
    @Mapping(source = "map", target = "bProperty", qualifiedByName= "xyz" )
    Target toTarget(Map<String,Map<String, Object>> map);

    @Named("abc")
    default String abc(Map<String,Map<String, Object>> in) {
        // return whatever need to return
    }

    @Named("xyz")
    default String xyz(Map<String,Map<String, Object>> in) {
        // return whatever need to return
    }
}
 

Комментарии:

1. Эта альтернатива работает! Было бы по-прежнему интересно узнать основную причину опубликованной проблемы, хотя

2. Интересно, я думаю, что пример в вопросе должен сработать. @helpme не могли бы вы, пожалуйста, поднять этот вопрос в проекте mapstruct, чтобы мы могли взглянуть на это?

3. @Филип, Должно быть, есть какая-то важность Translator класса, верно? Хотя я попробую оба вышеперечисленных сценария с моей стороны.

4. Это многоуровневые квалификаторы. Один на вершине класса, а другой на методе, но он также должен работать и без него, в основном он должен быть идентичен @Named

Ответ №2:

Похоже, это работает правильно при использовании 1.4.2.Final. Однако в версии 1.5.0.Beta1 это больше не работает из-за недавно введенной карты для сопоставления компонентов. Мы исследуем, как мы можем это исправить.

Однако есть альтернативное решение, которое позволит сделать эту работу правильной:

 @Mapper( uses = MappingUtil.class )
public interface SourceTargetMapper {

    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping(source = "first_property", target = "aProperty", qualifiedBy = A.class )
    @Mapping(source = "first_property", target = "bProperty", qualifiedBy = B.class )
    Target toTarget(Map<String,Map<String, Object>> map);
}


public class MappingUtil {

  @Qualifier
  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.CLASS)
  public @interface A {
  }

  @Qualifier
  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.CLASS)
  public @interface B {
  }

  @A
  public String abc(Map<String, Object> in) {
     return (String) in.get("a_second_key");
  }

  @B
  public String xyz(Map<String, Object> in) {
     return (String) in.get("b_second_key");
  }

}
 

Обратите внимание на изменение подписей в MappingUtil и изменение в Mapping#source .