Search code examples
javaspringproject-reactorreactivespring-webclient

The correct way to map a REST response to a Mono<SomeClass> using WebClient/ Spring Boot 2


I have a controller which is asking a service to reach a service endpoint to get a list of account numbers, then reach out to another service for each account number and retrieve additional data about that account. The 'happy path' essentially looks like this:

CONTROLLER

@ApiOperation(value = "getRelatedAccounts")
@GetMapping(value = "/relatedaccounts")
public ResponseEntity<RelatedAccounts> getRelatedAccounts(@RequestParam String accountNumber) {
    return new ResponseEntity<>(myService.getRelatedAccounts(accountNumber), HttpStatus.OK);
}

SERVICE

public RelatedAccounts getRelatedAccounts(String accountNumber) {
    // blah blah, hit the endpoint and get my response of a list of account numbers in relatedAccountsList
    Flux<AccountInformation> accountInfoFlux = Flux.fromIterable(relatedAccountsList)
        .parallel()
        .runOn(Schedulers.elastic())
        .flatMap(this::getAccountInformation)
        .ordered(Comparator.comparing(RelatedAccounts::getCorrectOrder)) // blah blah, convert from ParallelFlux to Flux
}

OTHER SERVICE

public Mono<AccountInformation> getAccountInformation(String accountNumber) {
    WebClient webClient = WebClient.builder()
            .baseUrl("http://myurl.com")
            .build();

    return webClient
            .get()
            .uri(uriBuilder -> uriBuilder
                    .queryParam("accountNumber", accountNumber)
                    .build()
            ).retrieve()
            .bodyToMono(AccountInformation.class) // This doesn't work for us, we get a very complex object as a response and need to parse a few fields.
}

I've spent all day on Google and I don't see anyone else parsing the response they get back, they just magically map it directly onto a perfectly created class. We don't have that option, we need to pluck the accountName from the response body and put it in the AccountInformation object. Does anyone have any idea how to do this?


Solution

  • We ended up doing it a pretty ugly, but extensible and effective way. In our OTHER SERVICE:

    public Mono<AccountInformation> getAccountInformation(String accountNumber) {
        WebClient webClient = WebClient.builder()
                .baseUrl("http://myurl.com")
                .build();
    
        return webClient
                .get()
                .uri(uriBuilder -> uriBuilder
                        .queryParam("accountNumber", accountNumber)
                        .build()
                ).retrieve()
                .bodyToMono(String.class) // just get the response as a JSON string
                .map(jsonString -> {
                    ObjectMapper mapper = new ObjectMapper();
                    JsonNode root = mapper.readTree(jsonString);
                    // parse/ map out all your information here
                    return myCustomMappedObject;
                })
    }