I have a List
of objects A
, where the object A
has this form:
class A {
private String a1;
private String a2;
private String a3;
private String a4;
private String a5;
private String a6;
}
I need to group this List first by a1
and a2
, then by a3
and a4
, resulting into List<B>
Where the object B
has this form
class B {
private String a1;
private String a2;
private List<C> list;
}
And where the object C
has this form
class C {
private String a3;
private String a4;
private List<D> list;
}
And where the object D
has this form
class D {
private String a5;
private String a6;
}
Example. Given the list:
[{a1="100", a2="bbb", a3="100100", a4="ddd", a5="1", a6="10"},
{a1="100", a2="bbb", a3="100100", a4="ddd", a5="2", a6="20"},
{a1="100", a2="bbb", a3="100200", a4="eee", a5="3", a6="30"},
{a1="200", a2="ccc", a3="200100", a4="fff", a5="4", a6="40"},
{a1="200", a2="ccc", a3="200200", a4="ggg", a5="5", a6="50"},
{a1="200", a2="ccc", a3="200300", a4="hhh", a5="6", a6="60"}]
I need this structure as a result:
{"B": [
{
"a1": "100",
"a2": "bbb",
"C": [
{
"a3": "100100",
"a4": "ddd",
"D": [
{"a5": "1", "a6": "10"},
{"a5": "2", "a6": "20"}
]
},
{
"a3": "100200",
"a4": "eee",
"D": [
{"a5": "3", "a6": "30"}
]
}
]
},
{
"a1": "200",
"a2": "ccc",
"C": [
{
"a3": "200100",
"a4": "fff",
"D": [
{"a5": "4", "a6": "40"}
]
},
{
"a3": "200200",
"a4": "ggg",
"D": [
{"a5": "5", "a6": "50"}
]
},
{
"a3": "200300",
"a4": "hhh",
"D": [
{"a5": "6", "a6": "60"}
]
}
]
}
]
}
Is this possible with streams in Java 11? Any other ways to achieve this goal are welcome as well.
The code provided below does what required.
There's a problem though which derived from the way of how your data is structured:
a1
and a2
, a3
and a4
, etc. constitute self-contained units of information that have a particular meaning in your domain model, you shouldn't create such beasts like A
with huge a number of string fields (object A
must contain a collection of objects B
and that it). It's a faulty design, you should refine you classes.The solution below makes use of built-in collectors Collector.collectionAndThen()
and Collectors.groupingBy()
. Map.Entry
is utilized as an intermediate container of data in both groupingBy()
collectors (Map.Entry
objects are keys in both maps).
Functions are responsible for transforming these maps into list List<C>
and List<B>
.
public static void main(String[] args) {
List<A> aList = List.of(new A("100", "bbb", "100100", "ddd", "1", "10"),
new A("100", "bbb", "100100", "ddd", "2", "20"),
new A("100", "bbb", "100200", "eee", "3", "30"),
new A("200", "ccc", "200100", "fff", "4", "40"),
new A("200", "ccc", "200200", "ggg", "5", "50"),
new A("200", "ccc", "200300", "hhh", "6", "60"));
Function<Map<Map.Entry<String, String>, List<D>>, List<C>> mapToListC =
mapC -> mapC.entrySet().stream()
.map(entry -> new C(entry.getKey().getKey(),
entry.getKey().getValue(),
entry.getValue()))
.collect(Collectors.toList());
Function<Map<Map.Entry<String, String>, Map<Map.Entry<String, String>, List<D>>>, List<B>> mapToListB =
mapB -> mapB.entrySet().stream()
.map(entry -> new B(entry.getKey().getKey(),
entry.getKey().getValue(),
mapToListC.apply(entry.getValue())))
.collect(Collectors.toList());
List<B> bList = aList.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy((A a) -> Map.entry(a.getA1(), a.getA2()),
Collectors.groupingBy((A a) -> Map.entry(a.getA3(), a.getA4()),
Collectors.mapping((A a) -> new D(a.getA5(), a.getA6()),
Collectors.toList()))),
mapToListB));
bList.forEach(System.out::println);
}
Output
B{a1='200', a2='ccc'list=
C{a3='200300', a4='hhh'list=
D{a5='6', a6='60'}}
C{a3='200100', a4='fff'list=
D{a5='4', a6='40'}}
C{a3='200200', a4='ggg'list=
D{a5='5', a6='50'}}}
B{a1='100', a2='bbb'list=
C{a3='100200', a4='eee'list=
D{a5='3', a6='30'}}
C{a3='100100', a4='ddd'list=
D{a5='1', a6='10'}
D{a5='2', a6='20'}}}