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

#3306 Support for map keys that contain dots when maps are mapped #3469

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

thunderhook
Copy link
Contributor

Tackling #3066 for map keys containing dots.
It was tricky to not reintroduce #3144 again.

@filiphr
Copy link
Member

filiphr commented Dec 16, 2023

@thunderhook how about something like Map<String, Source> if we use dotted then this would work properly right now, right? However, with this PR this will no longer work, right?

@thunderhook
Copy link
Contributor Author

I was wondering why there was no test handling this. I just tried the following:

@Mapper
public interface MapToBeanFromMapWithSource {

    MapToBeanFromMapWithSource INSTANCE = Mappers.getMapper( MapToBeanFromMapWithSource.class );

    @Mapping(target = "targetName", source = "name")
    // @Mapping(target = "targetName", source = "source.name")
    // @Mapping(target = "targetName", source = "sourceA.name")
    // @Mapping(target = "targetName", source = "source.sourceA.name")
    Target toTarget(Map<String, Source> source);

    class Source {

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    class Target {

        String targetName;

        public String getTargetName() {
            return targetName;
        }

        public void setTargetName(String targetName) {
            this.targetName = targetName;
        }
    }

}
    @ProcessorTest
    @WithClasses(MapToBeanFromMapWithSource.class)
    void shouldMap() {
        Map<String, MapToBeanFromMapWithSource.Source> sourceMap = new HashMap<>();
        MapToBeanFromMapWithSource.Source sourceA = new MapToBeanFromMapWithSource.Source();
        sourceA.setName( "value" );
        sourceMap.put( "sourceA", sourceA );
        sourceMap.put( "sourceB", new MapToBeanFromMapWithSource.Source() );

        MapToBeanFromMapWithSource.Target target = MapToBeanFromMapWithSource.INSTANCE.toTarget( sourceMap );

        assertThat( target.getTargetName() ).isEqualTo( "value" );
    }

And no matter what, it always shows:

DiagnosticDescriptor: ERROR MapToBeanFromMapWithSource.java:22
Can't map property "MapToBeanFromMapWithSource.Source name" to "String targetName".
Consider to declare/implement a mapping method: "String map(MapToBeanFromMapWithSource.Source value)

So it seems like this has never worked out of the box. But maybe I was doing something wrong. Do you have a working example in the main branch?

@filiphr
Copy link
Member

filiphr commented Dec 17, 2023

@thunderhook when I try your test on main it works when one of the following mappings are used

@Mapping(target = "targetName", source = "sourceA.name")
@Mapping(target = "targetName", source = "source.sourceA.name")

The other two

@Mapping(target = "targetName", source = "name")
@Mapping(target = "targetName", source = "source.name")

do not work because when using name the type that you get is Source and thus the compile error to provide a mapping from Source to String. When using source.name it is the same as using name since the source parameter is called source.

@thunderhook
Copy link
Contributor Author

Thanks for testing. Looks like I made a typo or something.
Yes, you're right. It works on main, but not on my branch.

To be backwards compatible, we would need some sort of control, whether to use a dot as a parameter segment or as a key containing a dot. But that leads to other complexities.
Think of nested maps, where the top level needs to be treated as a parameter segment, and the nested map as a string. An intermediate method would be needed, and nesting like this would be error-prone: sourceA.innerMap.some.key.with.dots.person.firstName, which should be separated like this:
sourceA - innerMap - some.key.with.dots - person - firstName.

But, there may be another way: Using the dot is a common and regular pattern for parameter segments. It is more of an exception to use it as a string like "a.b". So the user may need to escape the dot, if it is meant to be a string, such as: "a\.b".
This is the same with regular expressions where the dot is also meant as a wildcard, and therefore needs to be escaped.

WDYT? Is there already some kind of escaping? I only know of the MappingConstants with their <...> naming syntax. But I don't think a qualifier like this such as <DOT> would be helpful.

@thunderhook
Copy link
Contributor Author

thunderhook commented Dec 24, 2023

Here's my take with the escaped character. It is now possible to uses nested mappings with dotted keys as I stated in my previous comment. Please have a look.

Here is an example of a nested mapper: https://github.com/mapstruct/mapstruct/pull/3469/files#diff-c6a0b5742b174a67682dcf0a6384868c29be04169fbabe8c1c7eed7b330084d8R22

@filiphr
Copy link
Member

filiphr commented Dec 27, 2023

I think having a special escape character like there is in regular expressions is completely fine. I'd try to find some time to review this. I would like to first focus on some of the remaining things for 1.6 before start to add some new functionality as well.

@thunderhook thunderhook added this to the 1.7.0 milestone May 29, 2024
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

Successfully merging this pull request may close these issues.

None yet

2 participants