3

I have following structure.

Request {
    List<Param> paramList;
    Date startDate;
    Date endDate;
}

Param {
    String paramId;
    List<DataParam> dataParams; 
}

DataParam {
    String dataId;
    List<String> values;
}

CustomRequest {
    List<NewDataParam> newDataParam;
    Date startDate;
    Date endDate;
}

NewDataParam {
 String paramId;
 String dataId;
----
}

Iterating over the request with paramList, I want to make map of paramId and generated request like Map<String, List<CustomRequest>>.

With following code, I got Map<String, List<List<CustomRequest>>>. Can someone suggest how to get Map<String, List<CustomRequest>>.

request.paramList().stream()
    .collect(Collectors.groupingBy(Param::getParamId, 
        Collectors.mapping(i -> i.dataParams().stream()
           .map(r -> customBuildMethod(i.dataParamId(), r, request)).collect(Collectors.toList()), Collectors.toList())));

private CustomRequest customBuildMethod(String paramId ...) {
    CustomRequest customRequest = new CustomRequest();
    //mapping
    return customRequest;
}
Amit
  • 451
  • 1
  • 6
  • 19

2 Answers2

5

If you can use Java 9, you can achieve your goal with Collectors.flatMapping:

request.paramList()
       .stream()
       .collect(Collectors.groupingBy(Param::getParamId, 
                                      Collectors.flatMapping(i -> i.dataParams().stream().map(r -> customBuildMethod(i.dataParamId(), r, request))), 
                                                            Collectors.toList())));

In Java 8, you can do the following:

Map<String, List<CustomRequest>> result = 
    request.paramList()
          .stream()
          .collect(Collectors.groupingBy(Param::getParamId, 
                                         Collector.of(ArrayList::new, 
                                                     (l,i)->l.addAll(i.dataParams().stream().map(r -> customBuildMethod(i.dataParamId(), r, request)).collect(Collectors.toList())),
                                                     (l1,l2)->{l1.addAll(l2);return l1;})));
Eran
  • 387,369
  • 54
  • 702
  • 768
  • I can not use java9 yet. Is there no way we can achieve this with java8? – Amit Feb 14 '18 at 07:43
  • 1
    @Amit I made a Java 8 attempt. I hope I have no typos, since I couldn't check this code. – Eran Feb 14 '18 at 07:57
  • 1
    @Amit you can use `flatMapping` in Java 8 too. You will find the necessary code at the end of [this answer](https://stackoverflow.com/a/39131049/2711488); it’s just twelve lines of code… – Holger Feb 15 '18 at 08:47
  • Thank you. It is very interesting solution. – Amit Feb 15 '18 at 12:19
4

Instead of Collectors.mapping() you can use Collectors.collectingAndThen() where in finisher function you can do a flatMap using list of data params. Something like that:

Map<String, List<CustomRequest>> map = params.stream()
        .collect(Collectors.groupingBy(
                Param::getParamId, Collectors.collectingAndThen(Collectors.toList(), it -> it.stream()
                        .flatMap(i -> i.getDataParams().stream())
                        .map(r -> new CustomRequest(Arrays.asList(new NewDataParam()), new Date(), new Date()))
                        .collect(Collectors.toList())
                ))
        );

System.out.println(map);

Full example I used for tests:

import java.util.*;
import java.util.stream.Collectors;

public class ListOfListsToMapOfStringAndListExample {

    public static void main(String[] args) {

        final List<Param> params = Arrays.asList(
                new Param("1", Arrays.asList(new DataParam("2", Arrays.asList("a", "b")))),
                new Param("3", Arrays.asList(new DataParam("4", Arrays.asList("c", "d")))),
                new Param("5", Arrays.asList(new DataParam("6", Arrays.asList("e", "f"))))
        );

        Map<String, List<CustomRequest>> map = params.stream()
                .collect(Collectors.groupingBy(
                        Param::getParamId, Collectors.collectingAndThen(Collectors.toList(), it -> it.stream()
                                .flatMap(i -> i.getDataParams().stream())
                                .map(r -> new CustomRequest(Arrays.asList(new NewDataParam()), new Date(), new Date()))
                                .collect(Collectors.toList())
                        ))
                );

        System.out.println(map);
    }


    static class Request {
        List<Param> paramList;
        Date startDate;
        Date endDate;

        public Request(List<Param> paramList, Date startDate, Date endDate) {
            this.paramList = paramList;
            this.startDate = startDate;
            this.endDate = endDate;
        }
    }

    static class Param {
        String paramId;
        List<DataParam> dataParams;

        public Param(String paramId, List<DataParam> dataParams) {
            this.paramId = paramId;
            this.dataParams = dataParams;
        }

        public String getParamId() {
            return paramId;
        }

        public List<DataParam> getDataParams() {
            return dataParams;
        }
    }

    static class DataParam {
        String dataId;
        List<String> values;

        public DataParam(String dataId, List<String> values) {
            this.dataId = dataId;
            this.values = values;
        }

        public String getDataId() {
            return dataId;
        }

        public List<String> getValues() {
            return values;
        }
    }

    static class CustomRequest {
        List<NewDataParam> newDataParam;
        Date startDate;
        Date endDate;

        public CustomRequest(List<NewDataParam> newDataParam, Date startDate, Date endDate) {
            this.newDataParam = newDataParam;
            this.startDate = startDate;
            this.endDate = endDate;
        }

        public List<NewDataParam> getNewDataParam() {
            return newDataParam;
        }

        public Date getStartDate() {
            return startDate;
        }

        public Date getEndDate() {
            return endDate;
        }

        @Override
        public String toString() {
            return "CustomRequest{" +
                    "newDataParam=" + newDataParam +
                    ", startDate=" + startDate +
                    ", endDate=" + endDate +
                    '}';
        }
    }

    static class NewDataParam {

        @Override
        public String toString() {
            return "NewDataParam{}";
        }
    }
}

Output:

{1=[CustomRequest{newDataParam=[NewDataParam{}], startDate=Wed Feb 14 09:03:21 CET 2018, endDate=Wed Feb 14 09:03:21 CET 2018}], 3=[CustomRequest{newDataParam=[NewDataParam{}], startDate=Wed Feb 14 09:03:21 CET 2018, endDate=Wed Feb 14 09:03:21 CET 2018}], 5=[CustomRequest{newDataParam=[NewDataParam{}], startDate=Wed Feb 14 09:03:21 CET 2018, endDate=Wed Feb 14 09:03:21 CET 2018}]}

Update:

In case of passing param.paramId to customBuildMethod you can even get rid of flatMap() onparam.dataParams and map your initial param object to a CustomRequest. Something like:

Map<String, List<CustomRequest>> map = params.stream()
        .collect(Collectors.groupingBy(
                Param::getParamId, Collectors.collectingAndThen(Collectors.toList(), it -> it.stream()
                        .map(param -> customBuildMethod(param.paramId, param.dataParams))
                        .collect(Collectors.toList())
                ))
        );

In this example I pass param.paramId and param.dataParams to this helper method which creates CustomRequest object. It can use a list of data params to create a new list of new data params objects:

private static CustomRequest customBuildMethod(String paramId, List<DataParam> params) {
    return new CustomRequest(
            params.stream().map(dataParam -> new NewDataParam()).collect(Collectors.toList()),
            new Date(),
            new Date()
    );
}

Or more directly according to your code:

Map<String, List<CustomRequest>> map = params.stream()
        .collect(Collectors.groupingBy(
                Param::getParamId, Collectors.collectingAndThen(Collectors.toList(), it -> it.stream()
                        .map(param -> customBuildMethod(param.paramId, r, request))
                        .collect(Collectors.toList())
                ))
        );
Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • Thank you for such detailed explanation. If you see my customBuild method, I need to pass the paramId to make `NewDataParam` in customRequest. How can we pass that information in `i` to custom method. – Amit Feb 14 '18 at 08:25
  • 2
    @Amit In this case things can be simplified and you get skip flatMap part and map param directly to custom request object. Please check my updated answer. – Szymon Stepniak Feb 14 '18 at 08:46
  • Thank you , I accepted above answer from Eran because I do not need to modify my existing customMethod. Your solution is also good. thank you – Amit Feb 14 '18 at 08:53