12

When using multiple arguments in a @Mapper, it seems that the @Context arguments is unreachable

public interface MyMapper {  
      @Mapping(target="target1", source="arg1.arg") //works
      @Mapping(target="target2", source="arg2") //works
      @Mapping(target="target3", source="arg2.arg") //works
      @Mapping(target="target2", source="context.arg") //NOT WORKING
      public MyTarget convert(Object arg1, Object arg2, @Context Object context);
      
}

I am trying to use and expression="" to work around it, but I can't get it to work.

Any suggestions?

I can see I am not the only one to ever wish this. https://github.com/mapstruct/mapstruct/issues/1280

Thanks

ikhvjs
  • 5,316
  • 2
  • 13
  • 36
Filip
  • 906
  • 3
  • 11
  • 33
  • Why do you need to pass a `@Context` and use it as source? Can you provide a bit more context? – Filip May 16 '19 at 06:17

4 Answers4

19

I ran into the same scenario as I needed a @Context param to be able to pass to a nested mapping function, but also wanted to use it as a source in a @Mapping. I was able to achieve this using expression as follows:

public interface MyMapper {

  @Mapping(target="target1", source="arg1")
  @Mapping(target="target2", source="arg2")
  @Mapping(target="target3", expression="java( contextArg )")
  public MyTarget convert(Object arg1, Object arg2, @Context Object contextArg);

}
ikhvjs
  • 5,316
  • 2
  • 13
  • 36
nalapoke
  • 370
  • 4
  • 11
7

To answer your second question:


public interface MyMapper {

  @Mapping(target="target1", source="arg1.arg")
  @Mapping(target="target2", ignore = true ) // leave to after mapping 
  MyTarget convert(Object arg1, @Context Object context);

  @AfterMapping
  default convert(Object arg1, @MappingTarget MyTarget target, @Context context) {
        target.setTarget2( convert ( context ) );
  } 

  // if you have multipe mappings, you could address them here
  @Mapping(target="target2", source="context.arg") 
  MyInnerTarget convert(Object arg1, Object context);
}

Sjaak
  • 3,602
  • 17
  • 29
5

By definition a @Context annotated object is not a source. It is context So you can't use it as source in the @Mapping(target="target2", source="context.arg")

Here is a related github issue with official answer: github issue

Vadim Kirilchuk
  • 3,532
  • 4
  • 32
  • 49
Sjaak
  • 3,602
  • 17
  • 29
  • And how to achieve the desired behavior then? – Vadim Kirilchuk Apr 05 '20 at 12:07
  • 4
    remove the `@Context` and MapStruct will consider it as source object. By marking it `@Context` you essentially tell MapStruct to ignore the parameter for mapping, but pass it along in underlying method calls. Also, although MapStruct can handle multiple source arguments, it cannot call multiple source parameter methods to handle nested mappings. The API would become unpredictable and the selection mechanism would be come to unwieldy. – Sjaak Apr 05 '20 at 13:30
  • Your suggestion works if I need parameter in one place only. But if I need parameter on both layers I will have to do a `map(Source source, Param param, @Context Param param)` which makes no sense to me.. It would be much easier to not ignore context parameter for mapping.. – Vadim Kirilchuk Apr 06 '20 at 08:42
  • 1
    The reason is stated in my comment above. However, you can stick to the `@Context` approach by delegation to a second method and an `@AfterMapping`. – Sjaak Apr 06 '20 at 13:12
  • Yes, I saw that approach somewhere in the official documentation. I just don't understand why creators don't want to include context in mapping.. Why they exclude it? Any ideas? – Vadim Kirilchuk Apr 07 '20 at 08:17
  • 2
    context == by definition context.. not a source parameter.. its sole purpose is being ignored by mapping – Sjaak Apr 07 '20 at 13:17
  • 1
    well, that's mapstruct team definition. context is something to be used as a context for all methods from the very first method where it is passed. Think of a batch job context or anything else. The very first class which has context passed as argument is more then welcome to read or put anything to context. The mapstruct issue is that top level argument can't be made a context in downstream and context parameter can't be used as source in the first mapping. I understand all the definitions and workarounds, but it doesn't feel right to me. Thanks for a valuable discussion, appreciate that – Vadim Kirilchuk Apr 08 '20 at 13:53
1

Not really clean, but it seems having the same object as source and context allows to use it both ways.

  @Mapping(target="target1", source="arg1.arg")
  @Mapping(target="target2", source="arg2")
  @Mapping(target="target3", source="arg2.arg")
  @Mapping(target="target4", source="contextAsSource.arg") 
  public MyTarget convert(Object arg1, Object arg2, Object contextAsSource, @Context Object context);

Here contextAsSource and context are the same.

Michael Laffargue
  • 10,116
  • 6
  • 42
  • 76