1

I have some DB data:

//  sequence       subject       unit1       unit2        unit3
//    '2'          'math1',      'root',     'root1',     'asdf'
//    '3'          'math1',      'root',     'root1',     '2fa'
//    '4'          'math1',      'root',     'root2',     '23fasdf'
//    '5'          'math1',      'cosin',    'tan1',      ''
//    '6'          'math1',      'cosin',    'tan1',      'a'
//    '7'          'math2',      'sigma',    'alt',       '22'
//    '8'          'math2',      'sigma',    'alt',       '2fa'
//    '9'          'math2',      'sigma',    'alt',       ''
//    '10          'math2',      'ak1',      'test',      'aaaf3'
//    '11          'math2',      'ak2',      'test2',     'af3vv'

And I call the Spring Data JPA using findall() like this:

List<Entity> call = findall()

How to I make a response like this:

[{
    math1: [{
        unit1: [{
            root: [{
                root1: ["asdf", "2fa"]
            }]
        }]
    }]
}]

I tried:

 Map<String, List<Entity>> listUp_subject = collect.stream()
             .collect(Collectors.groupingBy(Entity::getSubject));

... and ...

 Map<String, Set<Entity>> collect2 = collect.stream()
            .collect(Collectors.groupingBy(Entity::getSubject, Collectors.mapping(Entity::new, Collectors.toSet())));

But I can't make like that using Java Stream or by another way. How to make it?

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
jjosjoahu
  • 131
  • 7

1 Answers1

1

1. Collecting List<String> to Map<...> using Java Stream API

The provided JSON output structure is not compatible with Map<String, List<Entity>> but with multiple dictionaries nested as values. As far as I see, there are 3 levels: subject, unit1 and unit2. The final non-dictionary value is a List<String> of unit3.

Therefore, the desired output is Map<String, Map<String, Map<String, List<String>>>> and chained Collectors.groupingBy should be suitable for it:

Map<String, Map<String, Map<String, List<String>>>> map = call.stream().collect(
    Collectors.groupingBy(Entity::getSubject,                                    // Map<String, List<Entity>> by Subject
        Collectors.groupingBy(Entity::getUnit1,                                  // Map<String, Map<String, List<Entity>>> by subject, then unit1
            Collectors.groupingBy(Entity::getUnit2,                              // Map<String, Map<String, Map<String, List<Entity>>>> by subject, then unit1 and then unit2
                Collectors.mapping(Entity::getUnit3, Collectors.toList())))));   // Map<String, Map<String, Map<String, List<String>>>> by subject, then unit1, then unit2 and then values are mapped as List<String> of unit3

Notice the last line with Collectors.mapping, this one maps the structure List<Entity to List<String. All the mappers used are available as of Java 8 (with the API itself).

2. Converting output to JSON

First of all, note that your required sample output is invalid since it mixes the names of the fields with values.

I suppose you return the response through a REST endpoint. I recommend Spring IO: Building a RESTful Web Service to start reading about the the conversion in Spring from an object to JSON (emphasizes and edited object name by myself):

The XXX object must be converted to JSON. Thanks to Spring’s HTTP message converter support, you need not do this conversion manually. Because Jackson 2 is on the classpath, Spring’s MappingJackson2HttpMessageConverter is automatically chosen to convert the XXX instance to JSON.

... otherwise you can use Jackson or GSON or any other alternative "manually". This is out of the scope of this question and have been already asked and answered here and here. The output in JSON of the code above for your sample input is:

{
   "math1":{
      "root":{
         "root1": ["asdf", "2fa"],
         "root2": ["23fasdf"]
      },
      "cosin":{
         "tan1": ["","a"]
      }
   },
   "math2":{
      "sigma":{
         "alt": ["22", "2fa", ""]
      },
      "ak1":{
         "test": ["aaaf3"]
      },
      "ak2":{
         "test2": ["af3vv"]
      }
   }
}
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183