Home > database >  Java Create new Object using groupedBy with different variables
Java Create new Object using groupedBy with different variables

Time:10-01

I'm trying to figure out how to use groupedBy correctly. I have a List with grades, deviations and subjects. Deviation and Grade are returned in the same field and can be differentiated by type

subject grade_date grade type
Math 2022-03-01 1 g
Math 2022-03-01 0.5 d1
Math 2022-03-01 0.5 d2
German 2022-03-02 2 g
German 2022-03-02 0.3 d1
German 2022-03-02 0.2 d2
German 2022-05-01 1 g
German 2022-05-01 0.5 d1
German 2022-05-01 0.4 d2

My goal is it to group this list by Subject and create a new Object which looks like this.

public class MapObject {
    private String subject;
    List<Grade> g = new ArrayList<>();
    List<Grade> d1 = new ArrayList<>();
    List<Grade> d2 = new ArrayList<>();
}

Grade Object would look like this

public class Grade {
        private Float grade;
        private LocalDate gradeDate;
    }

And the result (in JSON) should look like this

[
    {
        "subject": "Math",
        "g": [
            {
                "grade": 1.0,
                "gradeDate": "2022-03-01",
            }
        ],
        "d1": [
            {
                "grade": 0.5,
                "gradeDate": "2022-03-01",
            }
        ],
        "d2": [
            {
                "grade": 0.5,
                "gradeDate": "2022-03-01",
            }
        ]
    },
    {
        "subject": "German",
        "g": [
            {
                "grade": 2.0,
                "gradeDate": "2022-03-02",
            },
            {
                "grade": 1.0,
                "gradeDate": "2022-05-01",
            }
        ],
        "d1": [
            {
                "grade": 0.3,
                "gradeDate": "2022-03-02",
            },
            {
                "grade": 0.5,
                "gradeDate": "2022-05-01",
            }
        ],
        "d2": [
            {
                "grade": 0.2,
                "gradeDate": "2022-03-02",
            },
            {
                "grade": 0.4,
                "gradeDate": "2022-05-01",
            }
        ]
    }
]

I tried to use the groupedBy as follow but I couldn't figure out how to split the lists to g[], d1[], d2[]

var temp = myList.stream()
           .collect(Collectors.groupingBy(SourceEntity::getSubject,
                    Collectors.mapping(s -> {
                       return new Grade(s.getGrade(), s.getGradeDate());
                    }, Collectors.toList())))
           .entrySet()
           .stream()
           .map(x -> new MapObject(x.getKey(), x.getValue()))
           .collect(Collectors.toList());

CodePudding user response:

As the downstream collector of groupingBy() you need to provide a collector which associate each type with a list of grades.

For that, you can either use another collector groupingBy() and pass a combination of mapping() toList() as its downstream (as already shown in this answer), or create a custom collector for that purpose.

In order to transform a Map<String,List<Grade>> (grades by type) into a MapObject you can introduce a utility method.

List<SourceEntity> myList = // initializing the list
        
List<MapObject> temp = myList.stream()
    .collect(Collectors.groupingBy(
        SourceEntity::getSubject,
        Collector.of(
            HashMap::new,
            (Map<String, List<Grade>> gradesByType, SourceEntity next) ->
                gradesByType.computeIfAbsent(next.getType(), k -> new ArrayList<>())
                .add(new Grade(next.getGrade(), next.gradeDate)),
            (left, right) -> {
                right.forEach((k, v) -> left.merge(k, v,
                    (oldV, newV) -> { oldV.addAll(newV); return oldV; }));
                return left;
            })
    ))
    .entrySet().stream()
    .map(entry -> MapObject.of(entry.getKey(), entry.getValue()))
    .toList();
public class MapObject {
    public static final String GRADE = "g";
    public static final String DEVIATION_1 = "d1";
    public static final String DEVIATION_2 = "d2";
    
    private String subject;
    private List<Grade> g = Collections.emptyList();
    private List<Grade> d1 = Collections.emptyList();
    private List<Grade> d2 = Collections.emptyList();
    
    // constructors getters
    
    public static MapObject of(String subject, Map<String, List<Grade>> gradesByType) {
        
        return new MapObject(subject,
            gradesByType.getOrDefault(GRADE, Collections.emptyList()),
            gradesByType.getOrDefault(DEVIATION_1, Collections.emptyList()),
            gradesByType.getOrDefault(DEVIATION_2, Collections.emptyList())
        );
    }
}

CodePudding user response:

You need to groupBy twice - once by subject, and once by type. This way, the different types are put into a map, and you can easily extract them as lists using get("g"), get("d1"), etc.

// Map<String, Map<String, List<Grade>>>
var map = list.stream().collect(Collectors.groupingBy(
    x -> x.getSubject(),
    Collectors.groupingBy(
        x -> x.getType(),
        Collectors.mapping(x -> new Grade(x.getGrade(), x.getGradeDate()), Collectors.toList())
    )
));

Then you can transform each entry into a MapObject:

var result = map.entrySet().stream().map(MapObject::fromMapEntry).toList();

where

@Data
@AllArgsConstructor
class Grade {
    private Float grade;
    private LocalDate gradeDate;
}

@Data
@AllArgsConstructor
class MapObject {
    private String subject;
    private List<Grade> g;
    private List<Grade> d1;
    private List<Grade> d2;

    private MapObject() {}

    public static MapObject fromMapEntry(Map.Entry<String, Map<String, List<Grade>>> entry) {
        var mapObject = new MapObject();
        mapObject.subject = entry.getKey();
        mapObject.g = Objects.requireNonNullElseGet(entry.getValue().get("g"), ArrayList::new);
        mapObject.d1 = Objects.requireNonNullElseGet(entry.getValue().get("d1"), ArrayList::new);
        mapObject.d2 = Objects.requireNonNullElseGet(entry.getValue().get("d2"), ArrayList::new);
        return mapObject;
    }
}
  • Related