Spring webflux webclient make another call while evaluating response from first call

Kardeen

I am calling an endpoint with webclient and want to map the response that I get to another object. While mapping that object I want to make additional calls for some parameters of the response.

My first call returns the following object

{
  "type": "Collection",
  "key": "some.key",
  "settings": [
    {
      "type": "Struct",
      "key": "steps",
      "value": [
        {
          "type": "Struct",
          "key": "1",
          "value": [
            {
              "type": "String",
              "key": "headline",
              "value": "someheadline"
            },
            {
              "type": "String",
              "key": "subheadline",
              "value": "somesubheadline"
            },
            {
              "type": "Link",
              "key": "link.to.another.object",
              "value": {
                "linkType": "Boilerplate",
                "key": "configurabletextkey"
              }
            }
          ]
        }
      ]
    },
    {
      "type": "Struct",
      "key": "commons",
      "value": [
        {
          "type": "String",
          "key": "mandatory.fields.text",
          "value": "Pflichtfelder"
        }
      ]
    }
  ]
}

I map that response like so:

webClient
        .get()
        .uri(
            uriBuilder ->
                uriBuilder
                    .path(headlessConfig.getEndpoint())
                    .pathSegment(contentId)
                    .build())
        .retrieve()
        .bodyToMono(Collection.class)
        .map(response -> {
                  return getCollectionContent(response);
                })

In the getCollectionContent method I iterate over the settings array and I extract data from the response and map it to my PageContent Object.

public class PageContent {

  private String pageId;

  private List<Message> messages;
}
public class Message {

  @NonNull private String key;

  @NonNull private String text;

  private Boolean containsHtml = false;
}

If the response contains the type "String" i'll just add the data to an Message Object and add it to the List of the PageContent.

Now to the problem. If the type is "Link" I want to make another call with webclient like above to the same endpoint to get the key and text for that object, create a Message Object out of it and add it to my existing List.

The corresponding code looks like so:

webClient
        .get()
        .uri(
            uriBuilder ->
                uriBuilder
                    .path(headlessConfig.getEndpoint())
                    .pathSegment(contentKey)
                    .build())
        .retrieve()
        .bodyToMono(ConfigurableText.class)
        .map(
                configurableTextResponse -> {
                  messages.add(
                      new Message(
                          prefix + configurableTextResponse.getKey(),
                          configurableTextResponse.getText(),
                          true));
                  return Mono.empty();
                })

Now when I try to do that nothing happens and I just receive the PageContent object without the message for the link.

In a blocking way with resttemplate this logic should work but I would like to get it to work with webclient.

Edit:

Code to iterate through the list and extract the message data:

private PageContent getCollectionContent(Collection response) {

    PageContent pageContent = new PageContent();
    pageContent.setPageId(response.getKey());

    List<Message> messages = new ArrayList<>();

    response
        .getSettings()
        .forEach(
            settingsItemsArray -> {
              var settingsItemList = (List<?>) settingsItemsArray.getValue();
              String prefix = settingsItemsArray.getKey() + ".";

              extractMessageText(prefix, (LinkedHashMap<?, ?>) settingsItemList.get(0), messages);
            });

    pageContent.setMessages(messages);

    return pageContent;
  }

Code to extract the MessageText, iterate further or get the missing text for a link type.

private void extractMessageText(
      String prefix, LinkedHashMap<?, ?> settingsItem, List<Message> messages) {

    String itemKey = (String) settingsItem.get(KEY);
    String itemType = (String) settingsItem.get(TYPE);

    switch (itemType) {
      case "String":
        messages.add(new Message(prefix + itemKey, (String) settingsItem.get(VALUE)));
        break;
      case "Struct":
        ((List<?>) settingsItem.get(VALUE))
            .forEach(
                structItems ->
                    extractMessageText(
                        prefix + settingsItem.get(KEY) + ".",
                        (LinkedHashMap<?, ?>) structItems,
                        messages));
        break;
      case "Link":
        webClient
        .get()
        .uri(
            uriBuilder ->
                uriBuilder
                    .path(headlessConfig.getEndpoint())
                    .pathSegment(contentKey)
                    .build())
        .retrieve()
        .bodyToMono(ConfigurableText.class)
        .map(
                configurableTextResponse -> {
                  messages.add(
                      new Message(
                          prefix + configurableTextResponse.getKey(),
                          configurableTextResponse.getText(),
                          true));
                  return Mono.empty();
                })
        break;
      default:
        break;
    }
  }
Nonika

I have changed some of your code to make it more compatible with the reactor pattern. I have changed the recursion into expandDeep and also used Jackson to parse the JSON. I hope this will give you some ideas how to solve your issue.

List<Message> messages = Flux
                .fromIterable(jsonNode.get("settings"))
                //expand the graph into a stream of flat data and track the address of the node with 'prefix'
                //expand/exapndDeep operators are alternatives of recursion in project reactor
                .expandDeep(parent -> {
                    String parentPrefix = Optional.ofNullable(parent.get("prefix")).map(JsonNode::asText)
                            .orElse(parent.get("key").asText());
                    String type = parent.get("type").asText();
                    if (type.equals("Struct")) {
                        return Flux.fromIterable(parent.get("value"))
                                .cast(ObjectNode.class)
                                .map(child -> child.put("prefix", parentPrefix + ":" + child.get("key").asText()));
                    }
                    return Mono.empty();
                })
                //we have to choose only leaf nodes aka String and Link nodes
                .filter(node -> Arrays.asList("String", "Link").contains(node.get("type").asText()))
                //now process expanded leaf nodes
                .flatMap(leaf -> {
                    if ("String".equals(leaf.get("type").asText())) {
                        return Mono.just(new Message(leaf.get("prefix").asText(), leaf.get("value").asText(), true));
                    }
                    if ("Link".equals(leaf.get("type").asText())) {
                        return webClient
                                .get()
                                .uri(
                                        uriBuilder ->
                                                uriBuilder
                                                        .pathSegment(leaf.get("key").asText())
                                                        .build())
                                .retrieve()
                                .bodyToMono(JsonNode.class)
                                .map(configurableTextResponse -> new Message(
                                        leaf.get("prefix") + configurableTextResponse.get("key").asText(),
                                        configurableTextResponse.get("text").asText(),
                                        true));
                    }
                    return Mono.empty();
                })
                // at this point we are getting stream of the Message objects from the Link/String nodes
                //collect them into a list
                .collectList()
                //we have to subscribe()/block() the mono to actually invoke the pipline.
                .block();

The main reason your code did nothing was that you were not subscribing to your WebClient pipeline.

EDIT:

change

  .map(response -> {
                  return getCollectionContent(response);
                })

to

.flatMap(response -> {
                      return getCollectionContent(response);
                    })

and return from getCollectionContent(response) Mono<PageContent> page

something like:

     // at this point we are getting stream of the Message objects from the Link/String nodes
                    //collect them into a list
                    .collectList()
                    .map(messages -> {
                        PageContent pageContent = new PageContent();
                        pageContent.setPageId(response.get("pageId").asText());
pageContent.setMessages(messages);
                        return pageContent;
                    });

after these changes, your getCollectionContent() will return a publisher Mono<PageContent> which will be subscribed from the flatMap operator.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Execute blocking JDBC call in Spring Webflux

Spring : call REST API after receiving response from another REST API

how to log Spring 5 WebClient call

Spring WebClient Call Two Dependent API

How to chain a webclient call on each items from a list from the first call

How do I log out the body of a failed response to a Spring WebFlux WebClient request while returning the response to the caller?

spring webflux webclient response convert list of string to string

on error do another call and retry in webflux

How to prevent Rcpp from evaluating 'call' objects

Combining several REST Call with Spring Webflux

Cache the result of a Mono from a WebClient call in a Spring WebFlux web application

Make an ajax while another call is executing a long job with php (Laravel)

Spring Boot Webclient - wait end response of multi call

How to make a second API call based on the first response?

Spring WebFlux - Why I have to wait for WebClient response?

How to make async call inside another async method in Webflux?

Spring WebClient : Call method in retry

Use Spring WebClient response in next API call?

Call another form functions from the first form

Using response from one API call to do another API Call

Evaluating a function call constructed from a string

Spring WebFlux and WebClient change response on error

WebFlux using WebClient for blocking REST call

Getting some httpHeader in spring webclient call

Make an API call from one container to another

Make: evaluating result of call: recipe commences before first target

Spring WebFlux - How to print response as String instead of object using WebClient

Make a second API call based on first api call response in React with hooks

Springboot webflux/webclient call restApi for a list in sequential order