Search code examples
javadictionaryjava-streammappingcollectors

Map from List<Object> to Map<Object, List<Integer>> of common property


I have the following Event object definition:

@Data
class Event{
    private int referenceId;
    private int messageId;
    private String comment;

    public Event(int referenceId, int messageId, String comment) {
        this.referenceId = referenceId;
        this.messageId = messageId;
        this.comment = comment;
    }
}

I am given the following input:

EventDTO event  = new Event(1, 1, "comment");
EventDTO event1 = new Event(1, 2, "comment");
EventDTO event3 = new Event(1, 3, "comment");
EventDTO event4 = new Event(1, 4, "comment");

List<EventDTO> events = List.of(event, event1, event3, event4);

All the input I have will have the same data except the messageId property. Since I need to transform this data into another object called EventMessages:

@Data
class ChangeReason{
    private int id;
    private String description;

    public ChangeReason(int id, String description) {
        this.id = id;
        this.description = description;
    }
}


@Data
class EventMessages{
    private int referenceId;
    private String comment;
    private List<ChangeReason> reasons;

    public EventMessages(int referenceId, String comment, List<ChangeReason> reasons) {
        this.referenceId = referenceId;
        this.comment = comment;
        this.reasons = reasons;
    }
}

The ChangeReason id is the messageId of Event class

The problem is that N objects of Event need to be mapped into just 1 object of EventMessages. I am sure that all properties among incoming Events are going to be the same expect for the messageId.

I do not really like the solution of getting the first object like this:

List<ChangeReason> reasons = events.stream().map(event -> new ChangeReason(event.getMessageId(), "")).collect(Collectors.toList());
EventMessages eventMessages = new EventMessages(events.get(0).getReferenceId(), events.get(0).getComment(), reasons);

I dont really like this implementation and i was wondering if there is a better way of achiveing this.

I am trying to get the following scenario of data structure: Map<Event,List< Integer> > eventsWithMessages

So the key Event will be the object from I will retreive the comment and referenceId information which is common among all objects and the value would be the Collected different List of messageIds of events List I am trying to get this using lambdas and streams

Doing this wouldn't work:

        Map<Event, List<Integer>> eventMessages = events.stream().collect
            (Collectors.groupingBy(e -> e, Collectors.mapping(e -> e.getMessageId(), Collectors.toList())));

Since the groupingBy key comparison is done by the object and the property of messageId is different...


Solution

  • Found a solution. I started working with mergeOperators and I thought this could be the solution for this problem I had in the past. I leave the code:

    First I needed to implement an EqualHashCode for the Event class:

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Event)) return false;
            Event event = (Event) o;
            return getReferenceId() == event.getReferenceId() && getComment().equals(event.getComment());
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(getReferenceId(), getComment());
        }
    

    Then i used this code on MergeFunction toMap()

        Map<Event, List<Integer>> eventsWithMessages = events.stream()
                .collect(Collectors.toMap(
                        event -> event,
                        event -> Collections.singletonList(event.getMessageId()), 
                        (list1, list2) -> { // Here is the magic
                            List<Integer> mergedList = new ArrayList<>(list1);
                            mergedList.addAll(list2);
                            return mergedList;
                        }
                ));