Search code examples
javaspringjdbcguavaspring-jdbc

Mapping result of guava multimap to Response Model


In my web service application, the following model serves as contract for UI for statistical information where for a given date, I need to provide a summary of how many accepted, rejected etc:

@Data
public class StatusResponse {
    private LocalDate processedDate;
    private int accepted;
    private int rejected;
    private int failed;
    private int other;
}

My database query returns results of the summaries as follows:

date        status              groupStatus     count

2020-01-01  ACC                 Accepted        3
2020-01-22  RJCT                Rejected        47
2020-01-22  NM                  Other           1
2020-01-23  NIY                 Failed          55

I have an enum Status:

public enum Status{

    ACCEPTED("Accepted"),
    REJECTED("Rejected"),
    OTHER("Other"),
    FAILED("Failed");

    private final String text;

    Status (final String text) {
        this.text = text;
    }
}

In my repository class, I query and retrieve the status summaries into Guava MultiMap object where the keys are date, and value is an EnumMap of status and it's count:

public Multimap<LocalDate, EnumMap<Status, Integer>> statusSummary(final Request search) {
    return this.namedParameterJdbcTemplate.query(this.QUERY,
            new MapSqlParameterSource()
                    .addValue("processedDate", search.getProcessedDate()),
            rs -> {
                final Multimap<LocalDate, EnumMap<Status, Integer>> statusMap = ArrayListMultimap.create();
                while (rs.next()) {
                    final EnumMap<Status, Integer> groupedStatusCount = new EnumMap<>(Status.class);
                    groupedStatusCount.put(fromValue(rs.getString("groupStatus")), rs.getInt("count"));
                    statusMap.put(rs.getDate("date").toLocalDate(), groupedStatusCount);
                }
                return statusMap;
            }
    );
}

So a key (the processedDate) can have the same status as value. For example, the map can contain:

key --> 2020-01-23
   val1 --> OTHER 23
   val2 --> ACCEPTED 2
   val3 --> OTHER 4

My question and what I am struggling to complete:

How can I map the results of statusMap to StatusResponse and provide the summaries for each date. So StatusResponse for the above is something like this:

{
    "processedDate": [
        2020,
        1,
        23
    ],
    "accepted": 2,
    "rejected": 0,
    "failed": 0,
    "other": 27,
    "date": "2020-01-23T00:00:00"
}

I am using Java 8, and would like some help how can I achieve this with stream operation and using groupBy in a functional style?

Additionally, I need to have further grouping where anything before today is grouped to today, and anything after today, grouped to today + 1.

Thank you


Solution

  • you can try the following approach. The summary can be stored in Map and unwrapped at root level using JsonAnyGetter annotation from Jackson. This will remove summary field from response and put inner values at root level.

    @Data
    @Builder
    public class StatusResponse {
        private LocalDate processedDate;
    
        @JsonAnyGetter
        private Map<Status, Integer> summary;
    }
    

    Now, let's convert MultiMap to the required format

    List<StatusResponse> responseList = data.entries().stream()
      .map(entry -> {
        Map<Status, Integer> summary = entry.getValue().entries().stream()
          .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b));
    
        return StatusResponse.builder()
          .processedDate(entry.getKey())
          .summary(summary)
          .build();
      }).collect(Collectors.toList());
    

    Now, we need to convert LocaleDate to the proper format. we can take use jackson-datatype-jsr310. To convert to format like 'yyyy-mm-dd', we can just register JavaTimeModule to ObjectMapper.

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    

    The output would be something like this.

    [{
       "processedDate":"2020-01-23",
        "FAILED":3,
        "ACCEPTED":2
    }]
    

    Note: if database returns all status, it will include in response even if there values are zero. So, to have all status field in response, just make all of them are part of multimap.

    You can further explore custom serializers to fine-tune date format. here