6

I'm starting using java 8 stream API. I would like to convert a list of "sql result set" to domain objects, i.e composite structure.

Domain objects : a user has a collection of permissions, each permission has a collection of year of applications. For example, John has 2 permissions (MODERATOR and DEV). its moderator permission is only applicable for 2014 and 2015 its dev permission of only applicable for 2014.

class User {
  // some primitives attributes
  List<Permission> permission;
}

class Permission {
  // some primitives attributes
  List<Integer> years;
}

Now I make a query and got a list of flat results, something like:

[1, "moderator", 2014]
[1, "moderator", 2015]
[1, "dev", 2014]
[2, "dev", 2010]
[2, "dev", 2011]
[2, "dev", 2012]

The 1 and 2 are userId.

I tried various construction but at the end it's more complex than fluent. And didn't work :)
I read in a Java 8 book that it's "simple" to build dompain objects with collectors.
I cried a little when I read that :'(

I tried

sb.collect(
    collectingAndThen(
        groupingBy(
            Mybean::getUserId,
            collectingAndThen(
                groupingBy(Monbean::getPermissionId, mapping(convertPermission, toList())),
                finisherFonction)
            ),
        convertUser)
);

and got one hell of generics compilation failure.

  • what's the best way to construct multi level composite domain objects using java 8 streams ?
  • is collectionAndThen / finisher a good idea?
  • or do you use only groupingBy followed by a mapping function?
  • do you transform the classifier to an object (sort of first level mapping function?)

Because at the end I want to get rid of the Map and got a List<User> result (I think I can add a map call on the entrySet to finish the transformation).

1
  • What are your "flat results"? Arrays of objects? Commented Dec 15, 2014 at 5:26

2 Answers 2

6

Let me offer you a few options and you decide which looks most clear to you. I am assuming that User constructor is User(int userId, List<Permission> permissions) and Permission constructor is Permission(String permissionId, List<Integer> years)

Option 1: The direct approach. Group by userid, construct a list of permissions for each userid and make User objects. Personally, I find this much nesting in collectors to be hard to follow.

List<User> users = beans.stream()
    .collect(
        groupingBy(
            MyBean::getUserid,
            collectingAndThen(
                groupingBy(
                    MyBean::getPermission,
                    mapping(MyBean::getYear, toList())
                ),
                t -> t.entrySet().stream()
                    .map(e -> new Permission(e.getKey(), e.getValue()))
                    .collect(toList())
            )
        )
    ).entrySet().stream()
    .map(e -> new User(e.getKey(), e.getValue()))
    .collect(toList());

Option 2: Same as above but make the permission collector separately for clarity.

Collector<MyBean, ?, List<Permission>> collectPermissions = collectingAndThen(
    groupingBy(MyBean::getPermission, mapping(MyBean::getYear, toList())),
    t -> t.entrySet().stream()
        .map(e -> new Permission(e.getKey(), e.getValue()))
        .collect(toList())
);

List<User> users = beans.stream()
    .collect(groupingBy(MyBean::getUserid, collectPermissions))
    .entrySet().stream()
    .map(e -> new User(e.getKey(), e.getValue()))
    .collect(toList());

Option 3: First roll the beans into a map of userid to map of permissionid to list of years (Map<Integer, Map<String, List<Integer>>). Then construct the domain objects out of the map

List<User> users = beans.stream().collect(
    groupingBy(
        MyBean::getUserid,
        groupingBy(
            MyBean::getPermission,
            mapping(MyBean::getYear, toList())
        )
    )
).entrySet().stream()
    .map(u -> new User(
            u.getKey(),
            u.getValue().entrySet().stream()
                .map(p -> new Permission(p.getKey(), p.getValue()))
                .collect(toList())
        )
    ).collect(toList());
Sign up to request clarification or add additional context in comments.

2 Comments

Hi, Thanks you for your answer. I play with the Collectors yesterday evening and got a better understanding of the API :) I didn't try your solution but seems to me that this can do the job ! A big thanks for your help. I agre with you : nested groupingBy are hard to follow and hard to write too (error type compilation). It seems easier (at least for me) to write unitary transformation methods an compose them. I'll give option 3 a try. What's your preference ?
I can add a little complexity : if I want to handle "null values". WHat's your favorite pattern ? mapping to a new Hashmap accumulator ? something else ? The suecase is User can have 0 or n permissions and a Permission can have 0 or n years. bye
2

The collectingAndThen combinator is designed for when the intermediate form for accumulation differs from the desired final form. This is the case for the joining() collector (where the intermediate form is a StringBuilder, but the final form is a String), and also can be used to wrap mutable collections with immutable wrappers after the collecting is done. So I don't think this is the tool you are looking for.

If you're looking for "permissions by user", this will do the trick:

Map<UserId, List<Permission>>
    queryResults.stream()
                .collect(qr -> qr.getId(), 
                         mapping(qr -> qr.getPermission() + ":" + qr.getDate(), 
                                 toList()));

This would result in:

1 -> [ moderator:2014, moderator:2015, dev:2014 ]
2 -> [ dev:2010, dev:2011, dev:2012 ]

The idea is: - You are grouping by "id" (the first field of your query) - For each record, you select some function of the remaining fields (here, I used string concat for clarity, but you could create an object holding the permission and year), and send that to the downstream collector - Use Collectors.toList() as the downstream collector, which will then be the "value" of the Map created by groupingBy.

1 Comment

Hi, thank you for your post. I understand this answer but it's not exactly what I was looking for. At the end the map values are not composite domain objects.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.